hardware_enclave/internal/wsl/
detect.rs1#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
2pub fn decode_wsl_output(bytes: &[u8]) -> String {
9 if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
10 return decode_utf16le(&bytes[2..]);
11 }
12
13 let nul_bytes = bytes.iter().filter(|&&b| b == 0).count();
14 if bytes.len() >= 4 && nul_bytes >= bytes.len() / 4 {
15 return decode_utf16le(bytes);
16 }
17
18 String::from_utf8_lossy(bytes).to_string()
19}
20
21fn decode_utf16le(bytes: &[u8]) -> String {
22 let u16s: Vec<u16> = bytes
23 .chunks_exact(2)
24 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
25 .collect();
26 String::from_utf16_lossy(&u16s)
27}
28
29#[derive(Debug, Clone)]
31pub struct WslDistro {
32 pub name: String,
34 pub home_path: Option<std::path::PathBuf>,
37}
38
39pub fn is_wsl() -> bool {
41 #[cfg(target_os = "linux")]
42 {
43 if std::env::var("WSL_DISTRO_NAME").is_ok() {
44 return true;
45 }
46 if let Ok(version) = std::fs::read_to_string("/proc/version") {
47 let lower = version.to_lowercase();
48 if lower.contains("microsoft") || lower.contains("wsl") {
49 return true;
50 }
51 }
52 false
53 }
54 #[cfg(not(target_os = "linux"))]
55 false
56}
57
58pub fn detect_distros() -> Vec<WslDistro> {
62 #[cfg(target_os = "windows")]
63 {
64 use crate::internal::core::timeout::{run_with_timeout, TimeoutResult};
65 use std::time::Duration;
66 let mut cmd = std::process::Command::new("wsl");
69 cmd.args(["--list", "--quiet"]);
70 let output = match run_with_timeout(cmd, Duration::from_secs(15)) {
71 Ok(TimeoutResult::Completed(o)) if o.status.success() => o,
72 _ => return Vec::new(),
73 };
74
75 let stdout = decode_wsl_output(&output.stdout);
76 let names: Vec<String> = stdout
77 .lines()
78 .map(|l| l.trim().to_string())
79 .filter(|l| !l.is_empty())
80 .collect();
81
82 names
83 .into_iter()
84 .map(|name| {
85 let home_path = get_distro_home(&name);
86 WslDistro { name, home_path }
87 })
88 .collect()
89 }
90 #[cfg(not(target_os = "windows"))]
91 Vec::new()
92}
93
94#[cfg(target_os = "windows")]
96fn get_distro_home(distro: &str) -> Option<std::path::PathBuf> {
97 let home = linux_home(distro)?;
98 if home.is_empty() {
99 return None;
100 }
101 Some(std::path::PathBuf::from(format!(
103 "\\\\wsl.localhost\\{distro}{home}"
104 )))
105}
106
107#[cfg(target_os = "windows")]
109pub(crate) fn linux_home(distro: &str) -> Option<String> {
110 use crate::internal::core::timeout::{run_with_timeout, TimeoutResult};
111 use std::time::Duration;
112 let mut cmd = std::process::Command::new("wsl");
113 cmd.args(["-d", distro, "-e", "sh", "-lc", r#"printf '%s' "$HOME""#]);
114 let output = match run_with_timeout(cmd, Duration::from_secs(15)) {
115 Ok(TimeoutResult::Completed(o)) => o,
116 _ => return None,
117 };
118 if !output.status.success() {
119 return None;
120 }
121 let home = decode_wsl_output(&output.stdout).trim().to_string();
122 if home.is_empty() {
123 return None;
124 }
125 Some(home)
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used, clippy::panic)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_is_wsl_false_on_non_linux() {
135 #[cfg(not(target_os = "linux"))]
137 assert!(!is_wsl());
138 }
139
140 #[test]
141 fn test_detect_distros_empty_on_non_windows() {
142 #[cfg(not(target_os = "windows"))]
144 assert!(detect_distros().is_empty());
145 }
146
147 #[test]
148 fn test_decode_wsl_output_utf16le_without_bom() {
149 let bytes = b"U\0b\0u\0n\0t\0u\0";
150 assert_eq!(decode_wsl_output(bytes), "Ubuntu");
151 }
152
153 #[test]
154 fn decode_wsl_output_pure_utf8_returns_as_is() {
155 let bytes = b"hello world";
156 assert_eq!(decode_wsl_output(bytes), "hello world");
157 }
158
159 #[test]
160 fn decode_wsl_output_empty_bytes_returns_empty_string() {
161 assert_eq!(decode_wsl_output(b""), "");
162 }
163
164 #[test]
165 fn decode_wsl_output_utf16le_with_bom_decoded() {
166 let bytes: &[u8] = &[0xFF, 0xFE, b'H', 0, b'i', 0];
168 assert_eq!(decode_wsl_output(bytes), "Hi");
169 }
170
171 #[test]
172 fn decode_wsl_output_only_bom_returns_empty() {
173 let bytes: &[u8] = &[0xFF, 0xFE];
174 assert_eq!(decode_wsl_output(bytes), "");
175 }
176
177 #[test]
178 fn decode_wsl_output_utf16le_bom_with_newline() {
179 let bytes: &[u8] = &[0xFF, 0xFE, b'A', 0, b'\n', 0];
181 assert_eq!(decode_wsl_output(bytes), "A\n");
182 }
183
184 #[test]
185 fn decode_wsl_output_ascii_no_nulls_treated_as_utf8() {
186 let bytes = b"Debian";
187 assert_eq!(decode_wsl_output(bytes), "Debian");
188 }
189
190 #[test]
191 fn decode_wsl_output_high_null_density_treated_as_utf16le() {
192 let bytes: &[u8] = &[b'A', 0, b'B', 0, b'C', 0, b'D', 0];
194 assert_eq!(decode_wsl_output(bytes), "ABCD");
195 }
196
197 #[test]
198 fn decode_wsl_output_low_null_density_treated_as_utf8() {
199 let bytes: &[u8] = &[b'A', b'B', b'C', b'D', b'E', b'F', b'G', 0];
201 let result = decode_wsl_output(bytes);
203 assert!(result.starts_with("ABCDEFG"));
204 }
205
206 #[test]
207 fn decode_wsl_output_utf16le_bom_multiline() {
208 let mut bytes = vec![0xFF_u8, 0xFE];
210 for ch in "Ubuntu\nDebian".encode_utf16() {
211 bytes.extend_from_slice(&ch.to_le_bytes());
212 }
213 let result = decode_wsl_output(&bytes);
214 assert!(result.contains("Ubuntu"));
215 assert!(result.contains("Debian"));
216 }
217
218 #[test]
219 fn decode_wsl_output_utf16le_without_bom_multiline() {
220 let mut bytes: Vec<u8> = Vec::new();
222 for ch in "Ubuntu\nDebian".encode_utf16() {
223 bytes.extend_from_slice(&ch.to_le_bytes());
224 }
225 let result = decode_wsl_output(&bytes);
226 assert!(result.contains("Ubuntu"));
227 assert!(result.contains("Debian"));
228 }
229
230 #[test]
231 fn decode_wsl_output_three_bytes_not_bom_not_utf16le() {
232 let bytes: &[u8] = &[b'A', 0, b'B'];
234 let result = decode_wsl_output(bytes);
236 assert!(result.contains('A'));
237 }
238
239 #[test]
240 fn decode_wsl_output_utf8_with_multibyte_char() {
241 let input = "Ubuntu 22.04 LTS";
242 assert_eq!(decode_wsl_output(input.as_bytes()), input);
243 }
244
245 #[test]
246 fn decode_utf16le_odd_length_ignores_trailing_byte() {
247 let bytes: &[u8] = &[0x41, 0x00, 0x42, 0x00, 0xFF];
250 assert_eq!(decode_utf16le(bytes), "AB");
251 }
252
253 #[test]
254 fn decode_utf16le_non_ascii_codepoint() {
255 let bytes: &[u8] = &[0xE9, 0x00];
257 assert_eq!(decode_utf16le(bytes), "\u{00E9}");
258 }
259}