Skip to main content

wslplugins_rs/api/
api_v1.rs

1#[cfg(doc)]
2use super::Error;
3use super::{Result, WSLCommand};
4use crate::api::errors::require_update_error::Result as UpReqResult;
5use crate::api::wsl_command::IntoCowUtf8UnixPath;
6use crate::{SessionID, UserDistributionID, WSLVersion};
7use std::ffi::OsStr;
8use std::fmt::{self, Debug};
9use std::mem::MaybeUninit;
10use std::net::TcpStream;
11use std::os::windows::io::FromRawSocket as _;
12use std::os::windows::raw::SOCKET;
13use std::path::Path;
14#[cfg(feature = "tracing")]
15use tracing::instrument;
16use typed_path::Utf8UnixPath;
17use widestring::U16CString;
18use windows_core::{Result as WinResult, HRESULT};
19use wslpluginapi_sys;
20use wslpluginapi_sys::windows_sys::Win32::Networking::WinSock::SOCKET as WinSocket;
21
22#[cfg(doc)]
23use crate::DistributionID;
24
25use wslpluginapi_sys::WSLPluginAPIV1;
26
27use super::utils::check_required_version_result;
28
29/// Represents a structured interface for interacting with the `WSLPluginAPIV1` API.
30///
31/// This struct encapsulates the methods provided by the `WSLPluginAPIV1` API, allowing
32/// idiomatic interaction with the Windows Subsystem for Linux (WSL).
33#[repr(transparent)]
34pub struct ApiV1(WSLPluginAPIV1);
35
36impl From<ApiV1> for WSLPluginAPIV1 {
37    #[inline]
38    fn from(value: ApiV1) -> Self {
39        value.0
40    }
41}
42
43impl From<WSLPluginAPIV1> for ApiV1 {
44    #[inline]
45    fn from(value: WSLPluginAPIV1) -> Self {
46        Self(value)
47    }
48}
49
50impl AsRef<WSLPluginAPIV1> for ApiV1 {
51    #[inline]
52    fn as_ref(&self) -> &WSLPluginAPIV1 {
53        &self.0
54    }
55}
56
57impl AsRef<ApiV1> for WSLPluginAPIV1 {
58    #[inline]
59    fn as_ref(&self) -> &ApiV1 {
60        // SAFETY: The layout of ApiV1 is transparent over WSLPluginAPIV1, so this cast is safe.
61        unsafe { &*std::ptr::from_ref::<Self>(self).cast::<ApiV1>() }
62    }
63}
64
65impl ApiV1 {
66    /// Retpurns the current version of the WSL API being used.
67    ///
68    /// This is useful for checking compatibility with specific API features.
69    ///
70    /// # Example
71    /// ```ignore
72    /// let api_v1: ApiV1 = ...;
73    /// let version = api_v1.version();
74    /// println!(
75    ///     "WSL API version: {}.{}.{}",
76    ///     version.Major, version.Minor, version.Revision
77    /// );
78    #[must_use]
79    #[inline]
80    pub fn version(&self) -> &WSLVersion {
81        self.0.Version.as_ref()
82    }
83
84    /// Create plan9 mount between Windows & Linux
85    /// Allows sharing a folder between the Windows host and the Linux environment.
86    ///
87    /// # Arguments
88    /// - `session`: The current WSL session.
89    /// - `windows_path`: The Windows path of the folder to be mounted.
90    /// - `linux_path`: The Linux path where the folder will be mounted.
91    /// - `read_only`: Whether the mount should be read-only.
92    /// - `name`: A custom name for the mount.
93    /// # Errors
94    /// This function returns a windows error when the mount fails.
95    /// # Example
96    /// ``` rust,ignore
97    /// api.mount_folder(&session, "C:\\path", "/mnt/path", false, "MyMount")?;
98    /// ```
99    #[doc(alias = "MountFolder")]
100    #[cfg_attr(feature = "tracing", instrument(level = "trace"))]
101    #[inline]
102    pub fn mount_folder<
103        WP: AsRef<Path> + std::fmt::Debug,
104        UP: AsRef<Utf8UnixPath> + std::fmt::Debug,
105    >(
106        &self,
107        session_id: SessionID,
108        windows_path: WP,
109        linux_path: UP,
110        read_only: bool,
111        name: &OsStr,
112    ) -> WinResult<()> {
113        let encoded_windows_path =
114            U16CString::from_os_str_truncate(windows_path.as_ref().as_os_str());
115        let encoded_linux_path = U16CString::from_str_truncate(linux_path.as_ref().as_str());
116        let encoded_name = U16CString::from_os_str_truncate(name);
117        // SAFETY:
118        // - `self.0.MountFolder` comes from the validated `WSLPluginAPIV1` struct provided by WSL.
119        //   The API guarantees that this function pointer is non-null for supported versions.
120        // - All `U16CString` instances (`encoded_windows_path`, `encoded_linux_path`, `encoded_name`)
121        //   ensure null-termination and valid UTF-16 encoding, so the raw pointers passed to the FFI
122        //   are valid for the duration of the call.
123        // - `session.id()` returns a valid `WSLSessionId` provided by WSL; it remains valid while the
124        //   session is active.
125        // - No aliasing or mutation of memory occurs while the function pointer is called.
126        //
127        // The only `unsafe` operation is the FFI call, which is trusted because it is executed under
128        // WSL's documented plugin API contract.
129        let result = unsafe {
130            self.0.MountFolder.unwrap_unchecked()(
131                u32::from(session_id),
132                encoded_windows_path.as_ptr(),
133                encoded_linux_path.as_ptr(),
134                i32::from(read_only),
135                encoded_name.as_ptr(),
136            )
137        };
138        HRESULT(result).ok()
139    }
140
141    pub(crate) unsafe fn execute_binary_internal(
142        &self,
143        session_id: SessionID,
144        path: &[u8],
145        args: &[*const u8],
146    ) -> WinResult<TcpStream> {
147        let mut socket = MaybeUninit::<WinSocket>::uninit();
148        // SAFETY: Calling ExecuteBinary is safe with agument correctly prepared.
149        let stream = unsafe {
150            HRESULT(self.0.ExecuteBinary.unwrap_unchecked()(
151                u32::from(session_id),
152                path.as_ptr(),
153                args.as_ptr().cast_mut(),
154                socket.as_mut_ptr(),
155            ))
156            .ok()?;
157
158            let socket = socket.assume_init();
159            TcpStream::from_raw_socket(socket as SOCKET)
160        };
161        Ok(stream)
162    }
163
164    /// Set the error message to display to the user if the VM or distribution creation fails.
165    #[cfg_attr(feature = "tracing", instrument(level = "trace"))]
166    pub(crate) fn plugin_error(&self, error: &OsStr) -> WinResult<()> {
167        let error_utf16 = U16CString::from_os_str_truncate(error);
168        HRESULT(
169            // SAFETY: We know the pointer is always valid if the API ref is valid
170            unsafe { self.0.PluginError.unwrap_unchecked()(error_utf16.as_ptr()) },
171        )
172        .ok()
173    }
174
175    pub(crate) unsafe fn execute_binary_in_distribution_internal(
176        &self,
177        session_id: SessionID,
178        distribution_id: UserDistributionID,
179        c_path: &[u8],
180        args: &[*const u8],
181    ) -> Result<TcpStream> {
182        self.check_required_version(&WSLVersion::new(2, 1, 2))?;
183        let mut socket = MaybeUninit::<WinSocket>::uninit();
184        let guid: wslpluginapi_sys::windows_sys::core::GUID = distribution_id.into();
185        // SAFETY: Calling ExecuteBinaryInDistribution is safe with agument correctly prepared.
186        let stream = unsafe {
187            HRESULT(self.0.ExecuteBinaryInDistribution.unwrap_unchecked()(
188                u32::from(session_id),
189                (&raw const guid),
190                c_path.as_ptr(),
191                args.as_ptr().cast_mut(),
192                socket.as_mut_ptr(),
193            ))
194            .ok()?;
195
196            let socket = socket.assume_init();
197            TcpStream::from_raw_socket(socket as SOCKET)
198        };
199        Ok(stream)
200    }
201
202    /// Creates a new [`WSLCommand`] associated with this API instance.
203    ///
204    /// This is the preferred way to construct a command to be executed inside WSL.
205    /// The returned [`WSLCommand`] is bound to:
206    /// - this API handle,
207    /// - the provided session,
208    /// - the specified Linux program path.
209    ///
210    /// # Parameters
211    /// - `session_id`: The WSL session in which the command will be executed.
212    /// - `program`: A Linux (UTF-8, Unix-style) path
213    ///
214    /// # Returns
215    /// A [`WSLCommand`] builder ready to be configured and executed.
216    ///
217    /// # Notes
218    ///
219    /// - The default execution target is [`DistributionID::System`].
220    /// - `argv[0]` defaults to the program path unless explicitly overridden.
221    #[inline]
222    pub fn new_command<'a, P: IntoCowUtf8UnixPath<'a>>(
223        &'a self,
224        session_id: SessionID,
225        program: P,
226    ) -> WSLCommand<'a> {
227        WSLCommand::new(self, session_id, program)
228    }
229
230    fn check_required_version(&self, version: &WSLVersion) -> UpReqResult<()> {
231        check_required_version_result(self.version(), version)
232    }
233}
234
235impl Debug for ApiV1 {
236    #[inline]
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        f.debug_struct("ApiV1")
239            .field("version", self.version())
240            .finish()
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::utils::test_transparence;
248
249    #[test]
250    fn test_layouts() {
251        test_transparence::<WSLPluginAPIV1, ApiV1>();
252    }
253}