Skip to main content

wslplugins_rs/api/
wsl_command.rs

1use super::super::api::{ApiV1, Result as ApiResult};
2use crate::{DistributionID, SessionID};
3use core::clone::Clone;
4use std::{borrow::Cow, iter::once, net::TcpStream};
5use typed_path::Utf8UnixPath;
6
7mod into_cow_utf8_unix_path;
8pub use into_cow_utf8_unix_path::IntoCowUtf8UnixPath;
9pub use prepared_wsl_command::PreparedWSLCommand;
10mod wsl_command_execution;
11pub use wsl_command_execution::WSLCommandExecution;
12
13#[cfg(feature = "smallvec")]
14use smallvec::SmallVec;
15
16#[cfg(doc)]
17use super::super::api::Error as ApiError;
18mod prepared_wsl_command;
19#[cfg(not(feature = "smallvec"))]
20type ArgVec<'a> = Vec<Cow<'a, str>>;
21
22#[cfg(feature = "smallvec")]
23type ArgVec<'a> = SmallVec<[Cow<'a, str>; 8]>;
24
25/// A prepared command to be executed inside WSL.
26///
27/// `WSLCommand` is a builder-style abstraction around the WSL Plugin API
28/// execution functions (`ExecuteBinary` / `ExecuteBinaryInDistribution`).
29///
30/// Instances of `WSLCommand` are created through [`ApiV1::new_command`],
31/// ensuring they are always tied to a valid API handle and session.
32///
33/// # Key points
34///
35/// - The program path is a **Linux path** (UTF-8, Unix-style) represented by
36///   [`Utf8UnixPath`].
37/// - Arguments are stored as `Cow<'a, str>` to minimize allocations.
38/// - `argv[0]` can be overridden; otherwise it defaults to the program path.
39/// - The execution target can be the system context or a specific distribution.
40///
41/// # Argument semantics
42///
43/// The underlying WSL Plugin API expects a NULL-terminated `argv` array:
44///
45/// ```text
46/// argv[0] = program name
47/// argv[1..] = user arguments
48/// ```
49///
50/// This type exposes:
51/// - [`WSLCommand::argv`] for iterating over the full argument vector,
52/// - [`WSLCommand::arg0`] / [`WSLCommand::with_arg0`] to override `argv[0]`.
53///
54/// # Examples
55///
56/// ## Basic execution
57///
58/// ```no_run
59/// # use wslplugins_rs::{SessionID};
60/// # use wslplugins_rs::api::{ApiV1, WSLCommandExecution};
61/// # fn demo(api: &ApiV1) -> Result<(), Box<dyn std::error::Error>> {
62/// let stream = api
63///     .new_command(SessionID::from(0), "/bin/cat")
64///     .with_arg("/proc/version")
65///     .execute()?;
66///
67/// # drop(stream);
68/// # Ok(())
69/// # }
70/// ```
71///
72/// ## Overriding `argv[0]`
73///
74/// Some programs inspect `argv[0]` to determine their behavior
75/// (for example `busybox`).
76///
77/// ```no_run
78/// # use wslplugins_rs::{SessionID};
79/// # use wslplugins_rs::api::{ApiV1, WSLCommandExecution};
80/// # fn demo(api: &ApiV1, session_id: SessionID) -> Result<(), Box<dyn std::error::Error>> {
81/// let stream = api
82///     .new_command(session_id, "/bin/busybox")
83///     .with_arg0("sh")
84///     .with_args(["-c", "echo hello"])
85///     .execute()?;
86///
87/// # drop(stream);
88/// # Ok(())
89/// # }
90/// ```
91///
92/// ## Executing in a user distribution
93///
94/// ```no_run
95/// # use wslplugins_rs::{DistributionID, UserDistributionID, SessionID};
96/// # use wslplugins_rs::api::{ApiV1, WSLCommandExecution};
97/// # fn demo(api: &ApiV1) -> Result<(), Box<dyn std::error::Error>> {
98/// let distro: UserDistributionID = "3B6F3C1E-9B4A-4F2C-8E7A-2A9C6D4E1F52".parse().unwrap();
99/// let stream = api
100///     .new_command(SessionID::from(0), "/bin/echo")
101///     .with_distribution_id(DistributionID::User(distro))
102///     .with_arg("hello")
103///     .execute()?;
104/// # Ok(())
105/// # }
106/// ```
107///
108/// # Notes
109///
110/// - [`WSLCommand::execute`] consumes the command and returns a connected
111///   [`TcpStream`] to the process stdin/stdout.
112/// - stderr is forwarded to `dmesg` on the Linux side.
113/// - This type performs no validation of the Linux path or arguments beyond UTF-8 handling.
114#[doc(alias = "ExecuteBinary")]
115#[doc(alias = "ExecuteBinaryInDistribution")]
116#[derive(Clone, Debug)]
117pub struct WSLCommand<'a> {
118    /// Reference to the API v1 handle used to perform the execution.
119    api: &'a ApiV1,
120
121    /// Session in which the program should be executed.
122    session_id: SessionID,
123
124    /// Target distribution selection.
125    ///
126    /// - `System` executes in the root namespace.
127    /// - `User(id)` executes in a user distribution identified by its GUID.
128    distribution_id: DistributionID,
129
130    /// Linux program path (UTF-8, Unix path).
131    path: Cow<'a, Utf8UnixPath>,
132
133    /// Optional override for `argv[0]`.
134    ///
135    /// If `None`, `argv[0]` defaults to `path.as_str()`.
136    arg0: Option<Cow<'a, str>>,
137
138    /// Additional arguments (`argv[1..]`).
139    args: ArgVec<'a>,
140}
141
142impl<'a> WSLCommand<'a> {
143    /// Creates a new command builder for the given program.
144    ///
145    /// - `program` is converted to a [`Utf8UnixPath`] using [`IntoCowUtf8UnixPath`].
146    /// - The default target is [`DistributionID::System`].
147    /// - `argv[0]` is the program path string unless overridden via [`WSLCommand::arg0`]
148    ///   or [`WSLCommand::with_arg0`].
149    pub(crate) fn new<P: IntoCowUtf8UnixPath<'a>>(
150        api: &'a ApiV1,
151        session_id: SessionID,
152        program: P,
153    ) -> Self {
154        Self {
155            api,
156            arg0: None,
157            args: ArgVec::new(),
158            path: program.into_cow_utf8_unix_path(),
159            distribution_id: DistributionID::System,
160            session_id,
161        }
162    }
163
164    /// Returns the program path as a [`Utf8UnixPath`].
165    #[inline]
166    #[must_use]
167    pub fn get_path(&self) -> &Utf8UnixPath {
168        self.path.as_ref()
169    }
170
171    /// Returns `argv[0]`.
172    ///
173    /// If `arg0` has not been overridden, this returns the program path string.
174    #[inline]
175    #[must_use]
176    pub fn get_arg0(&self) -> &str {
177        self.arg0.as_deref().unwrap_or(self.path.as_str())
178    }
179
180    /// Returns `true` if `argv[0]` is the default value (the program path).
181    #[inline]
182    #[must_use]
183    pub const fn is_standard_arg_0(&self) -> bool {
184        self.arg0.is_none()
185    }
186
187    /// Resets `argv[0]` to its default value (the program path).
188    #[inline]
189    pub fn reset_arg0(&mut self) -> &mut Self {
190        self.arg0 = None;
191        self
192    }
193
194    /// Sets `argv[0]` (builder-style, by mutable reference).
195    #[inline]
196    pub fn arg0<T: Into<Cow<'a, str>>>(&mut self, arg0: T) -> &mut Self {
197        self.arg0 = Some(arg0.into());
198        self
199    }
200
201    /// Sets `argv[0]` (builder-style, by value).
202    #[inline]
203    #[must_use]
204    pub fn with_arg0<T: Into<Cow<'a, str>>>(mut self, arg0: T) -> Self {
205        self.arg0 = Some(arg0.into());
206        self
207    }
208
209    /// Pushes one argument (`argv[n]`, `n >= 1`), builder-style by mutable reference.
210    #[inline]
211    pub fn arg<T: Into<Cow<'a, str>>>(&mut self, arg: T) -> &mut Self {
212        self.args.push(arg.into());
213        self
214    }
215
216    /// Pushes one argument (`argv[n]`, `n >= 1`), builder-style by value.
217    #[inline]
218    #[must_use]
219    pub fn with_arg<T: Into<Cow<'a, str>>>(mut self, arg: T) -> Self {
220        self.args.push(arg.into());
221        self
222    }
223
224    /// Extends arguments from an iterator, builder-style by mutable reference.
225    #[inline]
226    pub fn args<I>(&mut self, args: I) -> &mut Self
227    where
228        I: IntoIterator,
229        I::Item: Into<Cow<'a, str>>,
230    {
231        self.args.extend(args.into_iter().map(Into::into));
232        self
233    }
234
235    /// Extends arguments from an iterator, builder-style by value.
236    #[inline]
237    #[must_use]
238    pub fn with_args<I>(mut self, args: I) -> Self
239    where
240        I: IntoIterator,
241        I::Item: Into<Cow<'a, str>>,
242    {
243        self.args.extend(args.into_iter().map(Into::into));
244        self
245    }
246
247    /// Returns an iterator over user-provided arguments (`argv[1..]`).
248    #[inline]
249    #[must_use]
250    pub fn get_args(&self) -> impl ExactSizeIterator<Item = &str> {
251        self.args.iter().map(AsRef::as_ref)
252    }
253
254    /// Returns an iterator over the full argv (`argv[0]` + `argv[1..]`).
255    #[inline]
256    pub fn argv(&self) -> impl Iterator<Item = &str> {
257        once(self.get_arg0()).chain(self.args.iter().map(AsRef::as_ref))
258    }
259
260    /// Clears user-provided arguments (`argv[1..]`).
261    #[inline]
262    pub fn clear_args(&mut self) -> &mut Self {
263        self.args.clear();
264        self
265    }
266
267    /// Truncates user-provided arguments (`argv[1..]`) to length `i`.
268    #[inline]
269    pub fn truncate_args(&mut self, i: usize) -> &mut Self {
270        self.args.truncate(i);
271        self
272    }
273
274    /// Sets the distribution target (builder-style by mutable reference).
275    #[inline]
276    #[must_use]
277    #[allow(clippy::missing_const_for_fn, reason = "Useless const")]
278    pub fn distribution_id(&mut self, distribution_id: DistributionID) -> &mut Self {
279        self.distribution_id = distribution_id;
280        self
281    }
282
283    /// Sets the distribution target (builder-style by value).
284    #[inline]
285    #[must_use]
286    #[allow(clippy::missing_const_for_fn, reason = "Useless const")]
287    pub fn with_distribution_id(mut self, distribution_id: DistributionID) -> Self {
288        self.distribution_id = distribution_id;
289        self
290    }
291
292    /// Resets the distribution target to [`DistributionID::System`].
293    #[inline]
294    #[allow(clippy::missing_const_for_fn, reason = "Useless const")]
295    pub fn reset_distribution_id(&mut self) -> &mut Self {
296        self.distribution_id = DistributionID::System;
297        self
298    }
299
300    /// Returns the current distribution target.
301    #[inline]
302    #[must_use]
303    pub const fn get_distribution_id(&self) -> DistributionID {
304        self.distribution_id
305    }
306    /// Prepare a [`WSLCommand`] for execution, encoding the path and arguments into C-compatible formats.
307    #[inline]
308    #[must_use]
309    pub fn prepare(&self) -> PreparedWSLCommand<'a> {
310        PreparedWSLCommand::from(self)
311    }
312}
313
314impl WSLCommandExecution for WSLCommand<'_> {
315    #[inline]
316    fn execute(&self) -> ApiResult<TcpStream> {
317        self.prepare().execute()
318    }
319}