Skip to main content

wslplugins_rs/
wsl_distribution_information.rs

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