Skip to main content

wslplugins_rs/
distribution_information.rs

1//! # Distribution Information
2//!
3//! This module provides a safe abstraction for accessing information about a WSL distribution.
4//! It wraps the `WSLDistributionInformation` structure from the WSL Plugin API and implements
5//! the `CoreDistributionInformation` trait for consistent access to distribution details.
6//!
7//! ## Overview
8//! The `DistributionInformation` struct provides methods to retrieve:
9//! - Distribution ID
10//! - Distribution name
11//! - Package family name (if applicable)
12//! - Process ID (PID) of the init process (requires API version 2.0.5 or higher)
13//! - PID namespace
14
15#[cfg(doc)]
16use crate::api::errors::require_update_error::Error;
17use crate::api::{
18    errors::require_update_error::Result, utils::check_required_version_result_from_context,
19};
20use crate::core_distribution_information::CoreDistributionInformation;
21use crate::WSLVersion;
22use crate::{UserDistributionID, WSLContext};
23use std::ffi::OsString;
24use std::fmt::{self, Debug, Display};
25use std::hash::{Hash, Hasher};
26use std::os::windows::ffi::OsStringExt as _;
27use std::ptr;
28use windows_core::PCWSTR;
29
30/// Represents detailed information about a WSL distribution.
31///
32/// This struct wraps the `WSLDistributionInformation` from the WSL Plugin API and provides
33/// safe, idiomatic Rust access to its fields.
34#[repr(transparent)]
35pub struct DistributionInformation(wslpluginapi_sys::WSLDistributionInformation);
36
37impl AsRef<DistributionInformation> for wslpluginapi_sys::WSLDistributionInformation {
38    #[inline]
39    fn as_ref(&self) -> &DistributionInformation {
40        // SAFETY: conveting this kind of ref is safe as it is transparent
41        unsafe { &*ptr::from_ref::<Self>(self).cast::<DistributionInformation>() }
42    }
43}
44
45impl From<DistributionInformation> for wslpluginapi_sys::WSLDistributionInformation {
46    #[inline]
47    fn from(value: DistributionInformation) -> Self {
48        value.0
49    }
50}
51
52impl AsRef<wslpluginapi_sys::WSLDistributionInformation> for DistributionInformation {
53    #[inline]
54    fn as_ref(&self) -> &wslpluginapi_sys::WSLDistributionInformation {
55        &self.0
56    }
57}
58
59impl From<wslpluginapi_sys::WSLDistributionInformation> for DistributionInformation {
60    #[inline]
61    fn from(value: wslpluginapi_sys::WSLDistributionInformation) -> Self {
62        Self(value)
63    }
64}
65
66impl DistributionInformation {
67    /// Retrieves the PID of the init process.
68    ///
69    /// This requires API version 2.0.5 or higher. If the current API version does not meet
70    /// the requirement, an error is returned.
71    ///
72    /// # Returns
73    /// - `Ok(pid)`: The PID of the init process.
74    /// # Errors
75    /// [Error]: If the runtime version version is insufficient.
76    #[inline]
77    pub fn init_pid(&self) -> Result<u32> {
78        check_required_version_result_from_context(
79            WSLContext::get_current(),
80            &WSLVersion::new(2, 0, 5),
81        )?;
82        Ok(self.0.InitPid)
83    }
84
85    /// Retrieves the PID namespace for the distribution.
86    ///
87    /// # Returns
88    /// The PID namespace as a `u64`.
89    ///
90    #[inline]
91    #[must_use]
92    pub const fn pid_namespace(&self) -> u64 {
93        self.0.PidNamespace
94    }
95}
96
97impl CoreDistributionInformation for DistributionInformation {
98    #[inline]
99    fn id(&self) -> UserDistributionID {
100        self.0.Id.into()
101    }
102
103    #[inline]
104    fn name(&self) -> OsString {
105        // SAFETY: Name is known to be valid
106        unsafe { OsString::from_wide(PCWSTR::from_raw(self.0.Name).as_wide()) }
107    }
108
109    #[inline]
110    fn package_family_name(&self) -> Option<OsString> {
111        // SAFETY: check already inside
112        unsafe {
113            let ptr = self.0.PackageFamilyName;
114            if ptr.is_null() {
115                None
116            } else {
117                Some(OsString::from_wide(PCWSTR::from_raw(ptr).as_wide()))
118            }
119        }
120    }
121
122    #[inline]
123    fn flavor(&self) -> Result<Option<OsString>> {
124        check_required_version_result_from_context(
125            WSLContext::get_current(),
126            &WSLVersion::new(2, 4, 4),
127        )?;
128        // SAFETY: check already inside and before by versionning
129        unsafe {
130            let ptr = self.0.Flavor;
131            if ptr.is_null() {
132                Ok(None)
133            } else {
134                Ok(Some(OsString::from_wide(PCWSTR::from_raw(ptr).as_wide())))
135            }
136        }
137    }
138
139    #[inline]
140    fn version(&self) -> Result<Option<OsString>> {
141        check_required_version_result_from_context(
142            WSLContext::get_current(),
143            &WSLVersion::new(2, 4, 4),
144        )?;
145        // SAFETY: check did before by versionning.
146        unsafe {
147            let ptr = self.0.Version;
148            if ptr.is_null() {
149                Ok(None)
150            } else {
151                Ok(Some(OsString::from_wide(PCWSTR::from_raw(ptr).as_wide())))
152            }
153        }
154    }
155}
156
157impl<T> PartialEq<T> for DistributionInformation
158where
159    T: CoreDistributionInformation,
160{
161    /// Compares two distributions for equality based on their IDs.
162    #[inline]
163    fn eq(&self, other: &T) -> bool {
164        self.id() == other.id()
165    }
166}
167
168impl Hash for DistributionInformation {
169    /// Computes a hash based on the distribution's ID.
170    #[inline]
171    fn hash<H: Hasher>(&self, state: &mut H) {
172        self.id().hash(state);
173    }
174}
175
176impl Display for DistributionInformation {
177    #[inline]
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        // SAFETY: Name is known to be valid
180        unsafe {
181            write!(
182                f,
183                "{} {{{}}}",
184                PCWSTR::from_raw(self.0.Name).display(),
185                self.id()
186            )
187        }
188    }
189}
190
191impl Debug for DistributionInformation {
192    #[inline]
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        let mut dbg = f.debug_struct("DistributionInformation");
195        dbg.field("name", &self.name())
196            .field("id", &self.id())
197            .field("package_family_name", &self.package_family_name())
198            .field("pid_namespace", &self.pid_namespace());
199        let mut exhaustive = true;
200
201        if let Ok(pid) = self.init_pid() {
202            dbg.field("init_pid", &pid);
203        } else {
204            exhaustive = false;
205        }
206        if let Ok(flavor) = self.flavor() {
207            dbg.field("flavor", &flavor);
208        } else {
209            exhaustive = false;
210        }
211        if let Ok(version) = self.version() {
212            dbg.field("version", &version);
213        } else {
214            exhaustive = false;
215        }
216
217        if exhaustive {
218            dbg.finish()
219        } else {
220            dbg.finish_non_exhaustive()
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use crate::utils::test_transparence;
229
230    #[test]
231    fn test_layouts() {
232        test_transparence::<wslpluginapi_sys::WSLDistributionInformation, DistributionInformation>(
233        );
234    }
235}