1use std::io::Cursor;
4use std::path::PathBuf;
5
6use winreg_core::hive::Hive;
7
8#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
11pub enum DistroVersion {
12 Wsl1,
13 Wsl2,
14 Unknown,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
18pub enum DistroState {
19 Installed,
20 Running,
21 Unknown,
22}
23
24#[derive(Debug, Clone, serde::Serialize)]
25pub struct LxssDistro {
26 pub guid: String,
27 pub distribution_name: String,
28 pub package_family_name: Option<String>,
29 pub base_path: String,
30 pub state: DistroState,
31 pub version: DistroVersion,
32 pub default_uid: Option<u32>,
33 pub is_default: bool,
34}
35
36impl LxssDistro {
37 pub fn vhdx_path(&self) -> Option<PathBuf> {
42 if self.version != DistroVersion::Wsl2 {
43 return None;
44 }
45 let mut path = PathBuf::from(&self.base_path);
46 path.push("ext4.vhdx");
47 Some(path)
48 }
49}
50
51const LXSS_PATH: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Lxss";
54
55fn is_guid(name: &str) -> bool {
59 let s = name.trim();
60 if s.len() != 38 {
61 return false;
62 }
63 let b = s.as_bytes();
64 b[0] == b'{'
65 && b[37] == b'}'
66 && b[9] == b'-'
67 && b[14] == b'-'
68 && b[19] == b'-'
69 && b[24] == b'-'
70 && b[1..9].iter().all(|c| c.is_ascii_hexdigit())
71 && b[10..14].iter().all(|c| c.is_ascii_hexdigit())
72 && b[15..19].iter().all(|c| c.is_ascii_hexdigit())
73 && b[20..24].iter().all(|c| c.is_ascii_hexdigit())
74 && b[25..37].iter().all(|c| c.is_ascii_hexdigit())
75}
76
77fn str_val(key: &winreg_core::key::Key<'_>, name: &str) -> Option<String> {
78 key.value(name)
79 .ok()
80 .flatten()
81 .and_then(|v| v.as_string().ok())
82}
83
84fn u32_val(key: &winreg_core::key::Key<'_>, name: &str) -> Option<u32> {
85 key.value(name).ok().flatten().and_then(|v| v.as_u32().ok())
86}
87
88pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<LxssDistro> {
92 let lxss_key = match hive.open_key(LXSS_PATH) {
93 Ok(Some(k)) => k,
94 _ => return Vec::new(),
95 };
96
97 let default_guid = str_val(&lxss_key, "DefaultDistribution").unwrap_or_default();
98
99 let subkeys = match lxss_key.subkeys() {
100 Ok(s) => s,
101 Err(_) => return Vec::new(),
102 };
103
104 let mut distros = Vec::new();
105
106 for subkey in subkeys {
107 let guid = subkey.name();
108 if !is_guid(&guid) {
109 continue;
110 }
111
112 let distribution_name = match str_val(&subkey, "DistributionName") {
113 Some(n) => n,
114 None => continue,
115 };
116
117 let base_path = match str_val(&subkey, "BasePath") {
118 Some(p) => p,
119 None => continue,
120 };
121
122 let package_family_name = str_val(&subkey, "PackageFamilyName");
123
124 let state = match u32_val(&subkey, "State") {
125 Some(1) => DistroState::Installed,
126 Some(4) => DistroState::Running,
127 _ => DistroState::Unknown,
128 };
129
130 let version = match u32_val(&subkey, "Version") {
131 Some(1) => DistroVersion::Wsl1,
132 Some(2) => DistroVersion::Wsl2,
133 _ => DistroVersion::Unknown,
134 };
135
136 let default_uid = u32_val(&subkey, "DefaultUid");
137 let is_default = !default_guid.is_empty() && guid == default_guid;
138
139 distros.push(LxssDistro {
140 guid,
141 distribution_name,
142 package_family_name,
143 base_path,
144 state,
145 version,
146 default_uid,
147 is_default,
148 });
149 }
150
151 distros
152}