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}