winbrew_windows\system/
mod.rs1use std::ffi::{OsStr, OsString};
2use std::fmt;
3use std::os::windows::ffi::{OsStrExt, OsStringExt};
4use std::path::PathBuf;
5
6use crate::models::domains::install::Architecture;
7use crate::registry::read_product_type;
8use windows_sys::Win32::Foundation::CloseHandle;
9use windows_sys::Win32::Security::{
10 GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation,
11};
12use windows_sys::Win32::Storage::FileSystem::SearchPathW;
13use windows_sys::Win32::System::SystemInformation::{
14 GetNativeSystemInfo, PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM64,
15 PROCESSOR_ARCHITECTURE_INTEL, SYSTEM_INFO,
16};
17use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
18
19mod version;
20
21pub use version::windows_version_string;
22
23const NORMAL_PLATFORM_TAGS: &[&str] = &["windows.desktop", "windows.ltsc", "windows.universal"];
24const SERVER_PLATFORM_TAGS: &[&str] = &["windows.server"];
25
26#[must_use]
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct HostProfile {
30 pub is_server: bool,
32 pub architecture: Architecture,
34}
35
36impl HostProfile {
37 pub fn platform_tags(self) -> &'static [&'static str] {
39 if self.is_server {
40 SERVER_PLATFORM_TAGS
41 } else {
42 NORMAL_PLATFORM_TAGS
43 }
44 }
45}
46
47impl fmt::Display for HostProfile {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 let host_family = if self.is_server { "server" } else { "normal" };
50
51 write!(f, "{host_family} {}", self.architecture)
52 }
53}
54
55pub fn host_profile() -> HostProfile {
61 HostProfile {
62 is_server: read_product_type()
63 .as_deref()
64 .map(classify_product_type)
65 .unwrap_or(false),
66 architecture: native_architecture(),
67 }
68}
69
70pub fn is_elevated() -> bool {
72 let mut token = core::ptr::null_mut();
73
74 let token_opened =
75 unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } != 0;
76 if !token_opened {
77 return false;
78 }
79
80 let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
81 let mut return_length = 0u32;
82 let queried = unsafe {
83 GetTokenInformation(
84 token,
85 TokenElevation,
86 &mut elevation as *mut TOKEN_ELEVATION as *mut core::ffi::c_void,
87 core::mem::size_of::<TOKEN_ELEVATION>() as u32,
88 &mut return_length,
89 )
90 } != 0;
91
92 unsafe {
93 CloseHandle(token);
94 }
95
96 queried && elevation.TokenIsElevated != 0
97}
98
99pub fn search_path_file(file_name: &str) -> Option<PathBuf> {
104 search_path_file_with_path(file_name, None)
105}
106
107fn search_path_file_with_path(file_name: &str, search_path: Option<&OsStr>) -> Option<PathBuf> {
108 let file_name_wide: Vec<u16> = OsStr::new(file_name).encode_wide().chain(Some(0)).collect();
109 let search_path_wide =
110 search_path.map(|path| path.encode_wide().chain(Some(0)).collect::<Vec<u16>>());
111
112 let mut buffer_len = 260u32;
113
114 loop {
115 let mut buffer = vec![0u16; buffer_len as usize];
116 let written = unsafe {
117 SearchPathW(
118 search_path_wide
119 .as_ref()
120 .map_or(core::ptr::null(), |path| path.as_ptr()),
121 file_name_wide.as_ptr(),
122 core::ptr::null(),
123 buffer_len,
124 buffer.as_mut_ptr(),
125 core::ptr::null_mut(),
126 )
127 };
128
129 if written == 0 {
130 return None;
131 }
132
133 if written as usize >= buffer.len() {
134 buffer_len = written + 1;
135 continue;
136 }
137
138 buffer.truncate(written as usize);
139 return Some(PathBuf::from(OsString::from_wide(&buffer)));
140 }
141}
142
143fn classify_product_type(product_type: &str) -> bool {
144 let trimmed = product_type.trim();
145
146 trimmed.eq_ignore_ascii_case("servernt") || trimmed.eq_ignore_ascii_case("lanmannt")
147}
148
149fn native_architecture() -> Architecture {
150 let mut system_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
151
152 unsafe {
153 GetNativeSystemInfo(&mut system_info as *mut SYSTEM_INFO);
154 }
155
156 architecture_from_native_system_info(system_info)
157}
158
159fn architecture_from_native_system_info(system_info: SYSTEM_INFO) -> Architecture {
160 let processor_architecture = unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture };
161
162 match processor_architecture {
163 PROCESSOR_ARCHITECTURE_AMD64 => Architecture::X64,
164 PROCESSOR_ARCHITECTURE_INTEL => Architecture::X86,
165 PROCESSOR_ARCHITECTURE_ARM64 => Architecture::Arm64,
166 _ => Architecture::Any,
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{
173 HostProfile, architecture_from_native_system_info, classify_product_type,
174 search_path_file_with_path,
175 };
176 use crate::models::domains::install::Architecture;
177 use std::fs;
178 use tempfile::tempdir;
179 use windows_sys::Win32::System::SystemInformation::{
180 PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM64, PROCESSOR_ARCHITECTURE_INTEL,
181 SYSTEM_INFO,
182 };
183
184 fn system_info_for(architecture: u16) -> SYSTEM_INFO {
185 let mut system_info = unsafe { std::mem::zeroed::<SYSTEM_INFO>() };
186 system_info.Anonymous.Anonymous.wProcessorArchitecture = architecture;
187 system_info
188 }
189
190 fn host_profile(is_server: bool, architecture: Architecture) -> HostProfile {
191 HostProfile {
192 is_server,
193 architecture,
194 }
195 }
196
197 #[test]
198 fn classifies_server_product_types() {
199 assert!(classify_product_type("ServerNT"));
200 assert!(classify_product_type("LanmanNT"));
201 assert!(!classify_product_type("WinNT"));
202 }
203
204 #[test]
205 fn classifies_server_product_types_case_insensitively_and_with_whitespace() {
206 assert!(classify_product_type(" SERVERNT "));
207 assert!(classify_product_type(" lanmannt"));
208 assert!(!classify_product_type(""));
209 assert!(!classify_product_type("Unknown"));
210 }
211
212 #[test]
213 fn host_profile_exposes_platform_tags_by_family() {
214 assert_eq!(
215 host_profile(false, Architecture::X64).platform_tags(),
216 &["windows.desktop", "windows.ltsc", "windows.universal"]
217 );
218 assert_eq!(
219 host_profile(true, Architecture::Arm64).platform_tags(),
220 &["windows.server"]
221 );
222 }
223
224 #[test]
225 fn maps_native_processor_architectures() {
226 assert_eq!(
227 architecture_from_native_system_info(system_info_for(PROCESSOR_ARCHITECTURE_AMD64)),
228 Architecture::X64
229 );
230 assert_eq!(
231 architecture_from_native_system_info(system_info_for(PROCESSOR_ARCHITECTURE_INTEL)),
232 Architecture::X86
233 );
234 assert_eq!(
235 architecture_from_native_system_info(system_info_for(PROCESSOR_ARCHITECTURE_ARM64)),
236 Architecture::Arm64
237 );
238 assert_eq!(
239 architecture_from_native_system_info(system_info_for(0xffff)),
240 Architecture::Any
241 );
242 }
243
244 #[test]
245 fn search_path_file_uses_explicit_search_path_list() {
246 let temp_dir = tempdir().expect("temp dir");
247 let file_path = temp_dir.path().join("7z.exe");
248
249 fs::write(&file_path, b"placeholder").expect("fake binary");
250
251 let found = search_path_file_with_path("7z.exe", Some(temp_dir.path().as_os_str()))
252 .expect("file should be found via explicit search path");
253
254 assert_eq!(found, file_path);
255 }
256}