Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 15 additions & 21 deletions plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use log_instrument::instrument;
use plugin::{Result, WSLPluginV1};
use std::{env, io::Read};
use windows::{
core::{Error as WinError, Result as WinResult, GUID},
core::{Error as WinError, Result as WinResult},
Win32::Foundation::E_FAIL,
};
use wslplugins_rs::wsl_user_configuration::bitflags::WSLUserConfigurationFlags;
Expand Down Expand Up @@ -58,12 +58,12 @@ impl WSLPluginV1 for Plugin {
) -> Result<()> {
let flags: WSLUserConfigurationFlags = user_settings.custom_configuration_flags().into();
info!("User configuration {:?}", flags);

let ver_args = ["/bin/cat", "/proc/version"];
match self
.context
.api
.execute_binary(session, ver_args[0], &ver_args)
.new_command(session, "/bin/cat")
.arg("/proc/version")
.execute()
{
Ok(mut stream) => {
let mut buf = String::new();
Expand All @@ -81,7 +81,7 @@ impl WSLPluginV1 for Plugin {
)
}
};
self.log_os_release(session, None);
self.log_os_release(session, DistributionID::System);
Ok(())
}

Expand All @@ -101,7 +101,7 @@ impl WSLPluginV1 for Plugin {
// Use unknow if init_pid not available
distribution.init_pid().map(|res| res.to_string()).unwrap_or("Unknow".to_string())
);
self.log_os_release(session, Some(distribution.id()));
self.log_os_release(session, DistributionID::User(distribution.id()));
Ok(())
}

Expand Down Expand Up @@ -132,21 +132,15 @@ impl WSLPluginV1 for Plugin {
}

impl Plugin {
fn log_os_release(&self, session: &WSLSessionInformation, distro_id: Option<GUID>) {
let args: [&str; 2] = ["/bin/cat", "/etc/os-release"];
let tcp_stream: std::result::Result<std::net::TcpStream, api::Error> = match distro_id {
Some(dist_id) => self
.context
.api
.execute_binary_in_distribution(session, dist_id, args[0], &args),
None => self
.context
.api
.execute_binary(session, args[0], &args)
.map_err(Into::into),
};
let result = tcp_stream;
match result {
fn log_os_release(&self, session: &WSLSessionInformation, distro_id: DistributionID) {
match self
.context
.api
.new_command(session, "/bin/cat")
.arg("/etc/os-release")
.distribution_id(distro_id)
.execute()
{
Ok(stream) => match OsRelease::from_reader(stream) {
Ok(release) => {
if let Some(version) = release.version() {
Expand Down
26 changes: 25 additions & 1 deletion wslplugins-rs/src/api/api_v1.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extern crate wslplugins_sys;
#[cfg(doc)]
use super::Error;
use super::Result;
use super::{Result, WSLCommand};
use crate::api::errors::require_update_error::Result as UpReqResult;
use crate::utils::{cstring_from_str, encode_wide_null_terminated};
use crate::wsl_session_information::WSLSessionInformation;
Expand Down Expand Up @@ -264,6 +264,30 @@ impl ApiV1 {
};
Ok(stream)
}
/// Creates a new `WSLCommand` instance tied to the current WSL API.
///
/// This method initializes a `WSLCommand` with the provided session and
/// program details. The program is specified as a path that can be converted
/// to a `Utf8UnixPath`.
///
/// # Parameters
/// - `session`: The session information associated with the WSL instance.
/// - `program`: A reference to the path of the program to be executed,
/// represented as an object implementing `AsRef<Utf8UnixPath>`.
///
/// # Returns
/// A new instance of `WSLCommand` configured to execute the specified program
/// within the provided WSL session.
///
/// # Type Parameters
/// - `T`: A type that implements `AsRef<Utf8UnixPath>`.
pub fn new_command<'a, T: AsRef<Utf8UnixPath> + ?Sized>(
&'a self,
session: &'a WSLSessionInformation<'a>,
program: &'a T,
) -> WSLCommand<'a> {
WSLCommand::new(self, session, program)
}

fn check_required_version(&self, version: &WSLVersion) -> UpReqResult<()> {
check_required_version_result(self.version(), version)
Expand Down
2 changes: 2 additions & 0 deletions wslplugins-rs/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ pub use errors::Result;
///
/// These utilities simplify common tasks, such as version checking or string manipulation.
pub mod utils;
mod wsl_command;
pub use wsl_command::WSLCommand;
221 changes: 221 additions & 0 deletions wslplugins-rs/src/api/wsl_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use typed_path::Utf8UnixPath;

#[cfg(doc)]
use super::super::api::Error as ApiError;
use super::super::api::{ApiV1, Result as ApiResult};
use crate::{DistributionID, WSLSessionInformation};
use std::net::TcpStream;
#[cfg(doc)]
use windows::core::GUID;

/// Represents a command to be executed in WSL.
///
/// The `WSLCommand` struct encapsulates details such as the program path, arguments,
/// and the associated distribution ID for execution.
#[derive(Clone)]
pub struct WSLCommand<'a> {
/// The WSL context associated with the command.
api: &'a ApiV1<'a>,
/// Arguments for the command.
args: Vec<&'a str>,
/// Path to the program being executed.
path: &'a Utf8UnixPath,
/// The distribution ID under which the command is executed.
distribution_id: DistributionID,
/// Session information for the current WSL session.
session: &'a WSLSessionInformation<'a>,
}

impl<'a> WSLCommand<'a> {
/// Creates a new `WSLCommand` instance.
///
/// This function initializes a `WSLCommand` with the necessary details to
/// execute a program in a WSL instance.
///
/// # Parameters
/// - `api`: A reference to the WSL API version 1.
/// - `session`: The session information associated with the WSL instance.
/// - `program`: A reference to the path of the program to be executed,
/// represented as an object implementing `AsRef<Utf8UnixPath>`.
///
/// # Returns
/// A new `WSLCommand` instance.
///
/// # Type Parameters
/// - `T`: A type that implements `AsRef<Utf8UnixPath>`.
pub(crate) fn new<T: AsRef<Utf8UnixPath> + ?Sized>(
api: &'a ApiV1<'a>,
session: &'a WSLSessionInformation<'a>,
program: &'a T,
) -> Self {
let my_program = program.as_ref();
let program_str = my_program.as_str();
Self {
api,
args: vec![program_str],
path: my_program,
distribution_id: DistributionID::System,
session,
}
}

/// Returns the path of the command.
pub fn get_path(&self) -> &'a Utf8UnixPath {
self.path
}

/// Sets the first argument (arg0) of the command.
///
/// # Parameters
/// - `arg0`: The new value for the first argument.
pub fn arg0<T: AsRef<str> + ?Sized>(&mut self, arg0: &'a T) -> &mut Self {
self.args[0] = arg0.as_ref();
self
}

/// Gets the first argument (arg0) of the command.
pub fn get_arg0(&self) -> &str {
self.args[0]
}

/// Checks if the first argument is the standard argument (the path).
pub fn is_standard_arg_0(&self) -> bool {
self.path == self.get_arg0()
}

/// Resets the first argument to the path of the command.
pub fn reset_arg0(&mut self) -> &mut Self {
self.args[0] = self.path.as_str();
self
}

/// Adds an argument to the command.
///
/// # Parameters
/// - `arg`: The argument to add.
pub fn arg<T: AsRef<str> + ?Sized>(&mut self, arg: &'a T) -> &mut Self {
self.args.push(arg.as_ref());
self
}

/// Adds multiple arguments to the command.
///
/// # Parameters
/// - `args`: An iterator of arguments to add.
pub fn args<I, T>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = &'a T>,
T: 'a + AsRef<str> + ?Sized,
{
self.args.extend(args.into_iter().map(AsRef::as_ref));
self
}

/// Returns an iterator over the arguments of the command, excluding arg0.
pub fn get_args(&self) -> impl ExactSizeIterator<Item = &str> {
self.args[1..].iter().copied()
}

/// Clears all arguments except arg0.
///
/// This method removes all additional arguments from the command,
/// effectively resetting the arguments to only include the program path.
///
/// # Returns
/// A mutable reference to the current `WSLCommand` instance.
///
/// # Example
/// ```rust ignore
/// command.arg("Hello").arg("World");
/// command.clear_args(); // Only arg0 ("/bin/echo") remains.
/// assert_eq!(command.get_args().count(), 0)
/// ```
pub fn crear_args(&mut self) -> &mut Self {
self.truncate_args(0);
self
}

/// Truncates the arguments of the command after a specified index.
///
/// This method keeps `arg0` and the first `i` additional arguments, discarding the rest.
///
/// # Parameters
/// - `i`: The index after which arguments will be removed. Note that `i = 0` keeps only `arg0`.
///
/// # Returns
/// A mutable reference to the current `WSLCommand` instance.
///
/// # Example
/// ```rust ignore
/// let mut command = WSLCommand::new(context, session, "/bin/echo");
/// command.arg("Hello").arg("World");
/// command.truncate_args(1); // Keeps only "/bin/echo" and "Hello".
/// ```
pub fn truncate_args(&mut self, i: usize) -> &mut Self {
self.args.truncate(i + 1);
self
}

/// Sets the distribution ID for the command.
///
/// # Parameters
/// - `distribution_id`: The new distribution ID to set.
pub fn distribution_id(&mut self, distribution_id: DistributionID) -> &mut Self {
self.distribution_id = distribution_id;
self
}

/// Resets the distribution ID to the system default.
pub fn reset_distribution_id(&mut self) -> &mut Self {
self.distribution_id = DistributionID::System;
self
}

/// Gets the current distribution ID for the command.
pub fn get_distribution_id(&self) -> DistributionID {
self.distribution_id
}

/// Executes the command and returns a [`TcpStream`].
///
/// This method determines the API call to be used based on the [`DistributionID`]:
/// - If [`DistributionID::System`], the method invokes [`execute_binary`](super::ApiV1::execute_binary).
/// - If [`DistributionID::User`], it invokes [`execute_binary_in_distribution`](super::ApiV1::execute_binary_in_distribution)
/// with the associated [GUID].
///
/// # Returns
/// - On success, it returns a [`TcpStream`] connected to the executed process, enabling interaction
/// with the process's stdin and stdout.
/// - On failure, an error indicating the cause of the failure.
///
/// # Errors
/// This function will return an error if:
/// - [`ApiError::RequiresUpdate`]: The WSL runtime version does not support targeting a user distribution.
/// - [`ApiError::WinError`]: The API call fails to execute the binary.
///
/// # Example
/// ```rust ignore
/// let mut command = ;
/// match WSLCommand::new(context, session, "/bin/echo").arg("Hello, World!").execute() {
/// Ok(stream) => {
/// // Interact with the process via the TcpStream.
/// },
/// Err(e) => eprintln!("Error: {}", e),
/// }
/// ```
pub fn execute(&mut self) -> ApiResult<TcpStream> {
let stream = match self.distribution_id {
DistributionID::System => {
self.api
.execute_binary(self.session, self.path, self.args.as_slice())?
}
DistributionID::User(id) => self.api.execute_binary_in_distribution(
self.session,
id,
self.path,
self.args.as_slice(),
)?,
};
Ok(stream)
}
}
Loading
Loading