diff --git a/Cargo.toml b/Cargo.toml index 5ae69ef..308e9ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,53 @@ description = "A Rust framework for developing WSL plugins using safe and idioma [patch.crates-io] wslpluginapi-sys = { version = "0.1.0-rc.1", git = "https://github.com/mveril/wslpluginapi-sys.git", branch = "develop" } + + +[workspace.lints.rust] +unused = "warn" +future_incompatible = "deny" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +cargo = {level = "warn", priority = -1} +# Enable some restriction +needless_pass_by_value = "warn" +implicit_clone = "warn" +unnecessary_wraps = "warn" +missing_const_for_fn = "warn" +missing_inline_in_public_items = "warn" +unnecessary_safety_doc = "deny" + +# ---- Strong DENY (CI-breaking, catch critical bugs) ---- +unwrap_used = "deny" # Avoid panics in production. +expect_used = "deny" # Same as unwrap, no `.expect`. +panic = "deny" # No panic! in library code. +todo = "deny" # No todo! in shipped code. +print_stdout = "deny" # Use logging, not println. +print_stderr = "deny" # Use logging, not eprintln. +dbg_macro = "deny" # No dbg! in release builds. +mem_forget = "deny" # Prevent memory leaks. +await_holding_lock = "deny" # Prevent async deadlocks with held locks. +exit = "deny" # Libraries must not kill the process. + +# ---- Useful WARN (signal, but not blocking) ---- +undocumented_unsafe_blocks = "warn" # Every unsafe block must be justified with SAFETY. +missing_assert_message = "warn" # Clear messages for all asserts. +integer_division = "warn" # Be explicit about truncating integer division. +indexing_slicing = "warn" # Can panic if index is wrong. +float_arithmetic = "warn" # Flag nondeterministic floating arithmetic. +mutable_key_type = "warn" # Prevent mutable keys in maps (subtle bugs). +manual_non_exhaustive = "warn" # Use #[non_exhaustive] instead of manual tricks. +wrong_self_convention = "warn" # Enforce idiomatic naming for as_/to_/into_ methods. +rc_mutex = "warn" +use_debug = "warn" + +[workspace.lints.rustdoc] +all = "warn" +broken_intra_doc_links = "forbid" +private_intra_doc_links = "deny" +invalid_rust_codeblocks = "deny" +bare_urls = "warn" +missing_crate_level_docs = "warn" diff --git a/wslplugins-macro-core/Cargo.toml b/wslplugins-macro-core/Cargo.toml index b09a28c..d50612f 100644 --- a/wslplugins-macro-core/Cargo.toml +++ b/wslplugins-macro-core/Cargo.toml @@ -5,6 +5,9 @@ version.workspace = true license.workspace = true repository.workspace = true edition = "2021" +description = "internal core implementation of for wslplugins-macro" +keywords = ["wsl", "plugin", "windows", "linux", "internal"] +categories = ["os::windows-apis", "api-bindings", "virtualization"] [dependencies] syn = { version = "*", features = ["full", "extra-traits"] } @@ -17,3 +20,6 @@ strum = { version = "0.26.3", features = ["derive"] } wslpluginapi-sys = { workspace = true, features = ["hooks-field-names"] } quote = "*" struct-field-names-as-array = "*" + +[lints] +workspace = true diff --git a/wslplugins-macro-core/README.md b/wslplugins-macro-core/README.md new file mode 100644 index 0000000..f8f068b --- /dev/null +++ b/wslplugins-macro-core/README.md @@ -0,0 +1,2 @@ +# wslplugins-macro-core +This package is a dependency of `wslplugins-macro` and provide the core of the macro logic it not intended to be installed manually \ No newline at end of file diff --git a/wslplugins-macro-core/build.rs b/wslplugins-macro-core/build.rs index 4640eaf..2013265 100644 --- a/wslplugins-macro-core/build.rs +++ b/wslplugins-macro-core/build.rs @@ -1,5 +1,6 @@ -use std::{env, fs::File, io::Write, path::PathBuf}; -use struct_field_names_as_array::FieldNamesAsSlice; +#![allow(missing_docs)] +use std::{env, fs::File, io::Write as _, path::PathBuf}; +use struct_field_names_as_array::FieldNamesAsSlice as _; use wslpluginapi_sys::WSLPluginHooksV1; fn main() -> Result<(), Box> { diff --git a/wslplugins-macro-core/src/generator/c_funcs_tokens.rs b/wslplugins-macro-core/src/generator/c_funcs_tokens.rs index 1e9cea1..176b56b 100644 --- a/wslplugins-macro-core/src/generator/c_funcs_tokens.rs +++ b/wslplugins-macro-core/src/generator/c_funcs_tokens.rs @@ -69,22 +69,7 @@ pub(super) fn get_c_func_tokens(hook: Hooks) -> Result> { }).unwrap_or(::wslplugins_rs::sys::windows_sys::Win32::Foundation::E_FAIL) } }), - Hooks::OnDistributionRegistered => Some(quote! { - extern "C" fn #c_method_ident( - session: *const ::wslplugins_rs::sys::WSLSessionInformation, - distribution: *const ::wslplugins_rs::sys::WSLOfflineDistributionInformation, - ) -> ::wslplugins_rs::sys::windows_sys::core::HRESULT { - let session_ptr = unsafe { &*session }; - let distribution_ptr = unsafe { &*distribution }; - PLUGIN.get().map(|plugin|{ - ::wslplugins_rs::windows_core::HRESULT::from(plugin.#trait_method_ident( - session_ptr.as_ref(), - distribution_ptr.as_ref(), - )).0 - }).unwrap_or(::wslplugins_rs::sys::windows_sys::Win32::Foundation) - } - }), - Hooks::OnDistributionUnregistered => Some(quote! { + Hooks::OnDistributionRegistered | Hooks::OnDistributionUnregistered => Some(quote! { extern "C" fn #c_method_ident( session: *const ::wslplugins_rs::sys::WSLSessionInformation, distribution: *const ::wslplugins_rs::sys::WSLOfflineDistributionInformation, diff --git a/wslplugins-macro-core/src/generator/hook_field_mapping.rs b/wslplugins-macro-core/src/generator/hook_field_mapping.rs index c27844b..2252789 100644 --- a/wslplugins-macro-core/src/generator/hook_field_mapping.rs +++ b/wslplugins-macro-core/src/generator/hook_field_mapping.rs @@ -32,7 +32,7 @@ fn generate_hook_fns(hooks: &[Hooks]) -> Result> { } // Create a static version of the type for plugin management -fn create_static_type(imp: &ParsedImpl) -> Result { +fn create_static_type(imp: &ParsedImpl) -> Type { let mut static_type = imp.target_type.as_ref().clone(); if let Some(lifetime) = utils::get_path_lifetime(&imp.trait_) { utils::replace_lifetime_in_type( @@ -41,7 +41,7 @@ fn create_static_type(imp: &ParsedImpl) -> Result { &Lifetime::new("'static", Span::call_site()), ); } - Ok(static_type) + static_type } // Prepare hooks by mapping them to their respective fields in the hook structure @@ -83,7 +83,7 @@ fn hook_field_mapping(hooks_struct_name: &Ident, hook: Hooks) -> Result Result { - let static_plugin_type = create_static_type(imp)?; + let static_plugin_type = create_static_type(imp); let hooks_ref_name = format_ident!("hooks_ref"); let hook_set = prepare_hooks(&hooks_ref_name, &imp.hooks)?; let RequiredVersion { diff --git a/wslplugins-macro-core/src/generator/mod.rs b/wslplugins-macro-core/src/generator/mod.rs index da77a9b..dbcc975 100644 --- a/wslplugins-macro-core/src/generator/mod.rs +++ b/wslplugins-macro-core/src/generator/mod.rs @@ -1,4 +1,4 @@ mod c_funcs_tokens; mod hook_field_mapping; mod utils; -pub(crate) use hook_field_mapping::generate; +pub use hook_field_mapping::generate; diff --git a/wslplugins-macro-core/src/generator/utils.rs b/wslplugins-macro-core/src/generator/utils.rs index 0e4612d..1ee3dd5 100644 --- a/wslplugins-macro-core/src/generator/utils.rs +++ b/wslplugins-macro-core/src/generator/utils.rs @@ -1,4 +1,7 @@ -use syn::*; +use syn::{ + GenericArgument, Lifetime, Path, PathArguments, Type, TypeBareFn, TypePath, TypeReference, + TypeTuple, +}; pub(super) fn replace_lifetime_in_type( ty: &mut Type, diff --git a/wslplugins-macro-core/src/hooks.rs b/wslplugins-macro-core/src/hooks.rs index ac6a716..ee0983f 100644 --- a/wslplugins-macro-core/src/hooks.rs +++ b/wslplugins-macro-core/src/hooks.rs @@ -1,22 +1,22 @@ -use heck::ToSnakeCase; -use strum::IntoEnumIterator; +use heck::ToSnakeCase as _; +use strum::IntoEnumIterator as _; include!(concat!(env!("OUT_DIR"), "/hooks.rs")); impl Hooks { - pub fn get_c_method_name(&self) -> String { + pub(crate) fn get_c_method_name(self) -> String { self.to_string().to_snake_case() } - pub fn get_hook_field_name(&self) -> String { + pub(crate) fn get_hook_field_name(self) -> String { self.to_string() } - pub fn get_trait_method_name(&self) -> String { + pub(crate) fn get_trait_method_name(self) -> String { self.to_string().to_snake_case() } - pub fn from_trait_method_name(trait_method_name: impl AsRef) -> Option { - Hooks::iter().find(|hook| hook.get_trait_method_name() == trait_method_name.as_ref()) + pub(crate) fn from_trait_method_name(trait_method_name: impl AsRef) -> Option { + Self::iter().find(|hook| hook.get_trait_method_name() == trait_method_name.as_ref()) } } diff --git a/wslplugins-macro-core/src/lib.rs b/wslplugins-macro-core/src/lib.rs index ef35688..3ce78c1 100644 --- a/wslplugins-macro-core/src/lib.rs +++ b/wslplugins-macro-core/src/lib.rs @@ -1,3 +1,8 @@ +#![allow(rustdoc::missing_doc)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] +#![allow(clippy::panic)] +#![allow(clippy::panic_in_result_fn)] mod generator; mod hooks; mod parser; @@ -9,8 +14,8 @@ use quote::quote; use syn::{parse2, Result}; use crate::parser::{ParsedImpl, RequiredVersion}; - -pub fn wsl_plugin_v1(attr: TokenStream, item: TokenStream) -> Result { +#[inline] +pub fn wsl_plugin_v1(attr: TokenStream, item: &TokenStream) -> Result { let parsed_impl_result = parse2::(item.clone()); let required_version_result = parse2::(attr); let (parsed_impl, required_version) = diff --git a/wslplugins-macro-core/src/parser/mod.rs b/wslplugins-macro-core/src/parser/mod.rs index aac4eab..936cbdf 100644 --- a/wslplugins-macro-core/src/parser/mod.rs +++ b/wslplugins-macro-core/src/parser/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod parsed_impl; -pub(crate) mod required_version; -pub(crate) use parsed_impl::ParsedImpl; -pub(crate) use required_version::RequiredVersion; +pub mod parsed_impl; +pub mod required_version; +pub use parsed_impl::ParsedImpl; +pub use required_version::RequiredVersion; diff --git a/wslplugins-macro-core/src/parser/parsed_impl.rs b/wslplugins-macro-core/src/parser/parsed_impl.rs index 198b798..d4eebd4 100644 --- a/wslplugins-macro-core/src/parser/parsed_impl.rs +++ b/wslplugins-macro-core/src/parser/parsed_impl.rs @@ -11,17 +11,13 @@ pub struct ParsedImpl { } impl Parse for ParsedImpl { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream<'_>) -> Result { let plugin_impl: ItemImpl = input.parse()?; - let p = - plugin_impl - .trait_ - .as_ref() - .map(|(_, path, _)| path) - .ok_or(syn::Error::new_spanned( - plugin_impl.impl_token, - "Expected a trait.", - ))?; + let p = plugin_impl + .trait_ + .as_ref() + .map(|(_, path, _)| path) + .ok_or_else(|| syn::Error::new_spanned(plugin_impl.impl_token, "Expected a trait."))?; let hook_vec: Vec = plugin_impl .items .iter() @@ -31,7 +27,7 @@ impl Parse for ParsedImpl { }) .collect(); - Ok(ParsedImpl { + Ok(Self { target_type: plugin_impl.self_ty.clone(), hooks: hook_vec.into_boxed_slice(), trait_: p.clone(), diff --git a/wslplugins-macro-core/src/parser/required_version.rs b/wslplugins-macro-core/src/parser/required_version.rs index a760b62..d5bc96c 100644 --- a/wslplugins-macro-core/src/parser/required_version.rs +++ b/wslplugins-macro-core/src/parser/required_version.rs @@ -12,7 +12,7 @@ pub struct RequiredVersion { pub revision: u32, } impl Parse for RequiredVersion { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream<'_>) -> Result { // Result of parsing the major version to u32 let major_lit = input.parse::()?; // Result of parsing the coma version to u32 @@ -21,7 +21,7 @@ impl Parse for RequiredVersion { let minor_lit = input.parse::()?; // Parse the revision if it exists let revision_lit = if input.peek(Token![,]) { - input.parse::().unwrap(); + input.parse::()?; Some(input.parse::()?) } else { None @@ -33,11 +33,9 @@ impl Parse for RequiredVersion { }?; let major_result = major_lit.base10_parse::(); let minor_result = minor_lit.base10_parse::(); - let revision_result = revision_lit - .map(|lit| lit.base10_parse::()) - .unwrap_or(Ok(0)); + let revision_result = revision_lit.map_or(Ok(0), |lit| lit.base10_parse::()); acc_syn_result!(major_result, minor_result, revision_result).map( - |(major, minor, revision)| RequiredVersion { + |(major, minor, revision)| Self { major, minor, revision, diff --git a/wslplugins-macro/Cargo.toml b/wslplugins-macro/Cargo.toml index 41f472d..ee8c604 100644 --- a/wslplugins-macro/Cargo.toml +++ b/wslplugins-macro/Cargo.toml @@ -5,6 +5,9 @@ version.workspace = true license.workspace = true repository.workspace = true edition = "2021" +description = "macro for wslplugins-rs" +keywords = ["wsl", "plugin", "windows", "linux", "internal"] +categories = ["os::windows-apis", "api-bindings", "virtualization"] [lib] proc-macro = true @@ -14,3 +17,5 @@ syn = "*" quote = "*" proc-macro2 = "*" wslplugins-macro-core = { path = "../wslplugins-macro-core" } +[lints] +workspace = true diff --git a/wslplugins-macro/README.md b/wslplugins-macro/README.md new file mode 100644 index 0000000..69793e2 --- /dev/null +++ b/wslplugins-macro/README.md @@ -0,0 +1,2 @@ +# wslplugins-macro-core +This package is an optional dependency of `wslplugins-rs` and provide macro for it's not intended to be installed manually \ No newline at end of file diff --git a/wslplugins-macro/src/lib.rs b/wslplugins-macro/src/lib.rs index 0163518..c9a5bdc 100644 --- a/wslplugins-macro/src/lib.rs +++ b/wslplugins-macro/src/lib.rs @@ -1,8 +1,17 @@ +#![allow(missing_docs)] use proc_macro::TokenStream; - +/// Attribute macro for WSL plugin V1. +/// This macro should be used on impl block of `WSLPluginV1` in order to register the plugin that implement this interface +/// # Example +/// ``` rust, ignore +/// #[wsl_plugin_v1] +/// impl WSLPluginV1 for MyPlugin { +/// // Implementation details +/// } +/// ``` #[proc_macro_attribute] pub fn wsl_plugin_v1(attr: TokenStream, item: TokenStream) -> TokenStream { - wslplugins_macro_core::wsl_plugin_v1(attr.into(), item.into()) + wslplugins_macro_core::wsl_plugin_v1(attr.into(), &item.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/wslplugins-rs/Cargo.toml b/wslplugins-rs/Cargo.toml index 0a94472..97642b1 100644 --- a/wslplugins-rs/Cargo.toml +++ b/wslplugins-rs/Cargo.toml @@ -6,7 +6,9 @@ license.workspace = true repository.workspace = true description.workspace = true edition = "2021" - +keywords = ["wsl", "plugin", "windows", "linux"] +categories = ["os::windows-apis", "api-bindings", "virtualization"] +readme.workspace = true [dependencies] windows-core = "0.61.2" @@ -21,6 +23,7 @@ widestring = { version = "1", features = ["alloc"] } wslplugins-macro = { path = "../wslplugins-macro", optional = true } wslpluginapi-sys.workspace = true + [dependencies.semver] version = ">0.1" optional = true @@ -33,3 +36,6 @@ macro = ["dep:wslplugins-macro", "sys"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/wslplugins-rs/src/api/api_v1.rs b/wslplugins-rs/src/api/api_v1.rs index bd2a433..89dea28 100644 --- a/wslplugins-rs/src/api/api_v1.rs +++ b/wslplugins-rs/src/api/api_v1.rs @@ -1,4 +1,3 @@ -extern crate wslpluginapi_sys; #[cfg(doc)] use super::Error; use super::Result; @@ -9,49 +8,57 @@ use crate::WSLVersion; #[cfg(feature = "log-instrument")] use log_instrument::instrument; use std::ffi::{CString, OsStr}; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::iter::once; use std::mem::MaybeUninit; use std::net::TcpStream; -use std::os::windows::io::FromRawSocket; +use std::os::windows::io::FromRawSocket as _; use std::os::windows::raw::SOCKET; use std::path::Path; +use std::ptr; use typed_path::Utf8UnixPath; use widestring::U16CString; use windows_core::{Result as WinResult, GUID, HRESULT}; +use wslpluginapi_sys; use wslpluginapi_sys::windows_sys::Win32::Networking::WinSock::SOCKET as WinSocket; use wslpluginapi_sys::WSLPluginAPIV1; use super::utils::check_required_version_result; -/// Represents a structured interface for interacting with the WSLPluginAPIV1 API. -/// This struct encapsulates the methods provided by the WSLPluginAPIV1 API, allowing +/// Represents a structured interface for interacting with the `WSLPluginAPIV1` API. +/// +/// This struct encapsulates the methods provided by the `WSLPluginAPIV1` API, allowing /// idiomatic interaction with the Windows Subsystem for Linux (WSL). #[repr(transparent)] pub struct ApiV1(WSLPluginAPIV1); impl From for WSLPluginAPIV1 { + #[inline] fn from(value: ApiV1) -> Self { value.0 } } impl From for ApiV1 { + #[inline] fn from(value: WSLPluginAPIV1) -> Self { - ApiV1(value) + Self(value) } } impl AsRef for ApiV1 { + #[inline] fn as_ref(&self) -> &WSLPluginAPIV1 { &self.0 } } impl AsRef for WSLPluginAPIV1 { + #[inline] fn as_ref(&self) -> &ApiV1 { - unsafe { &*(self as *const WSLPluginAPIV1 as *const ApiV1) } + // SAFETY: The layout of ApiV1 is transparent over WSLPluginAPIV1, so this cast is safe. + unsafe { &*std::ptr::from_ref::(self).cast::() } } } @@ -69,6 +76,8 @@ impl ApiV1 { /// version.Major, version.Minor, version.Revision /// ); #[cfg_attr(feature = "log-instrument", instrument)] + #[must_use] + #[inline] pub fn version(&self) -> &WSLVersion { self.0.Version.as_ref() } @@ -82,13 +91,15 @@ impl ApiV1 { /// - `linux_path`: The Linux path where the folder will be mounted. /// - `read_only`: Whether the mount should be read-only. /// - `name`: A custom name for the mount. - /// + /// # Errors + /// This function returns a windows error when the mount fails. /// # Example /// ``` rust,ignore /// api.mount_folder(&session, "C:\\path", "/mnt/path", false, "MyMount")?; /// ``` #[doc(alias = "MountFolder")] #[cfg_attr(feature = "log-instrument", instrument)] + #[inline] pub fn mount_folder, UP: AsRef>( &self, session: &WSLSessionInformation, @@ -101,12 +112,24 @@ impl ApiV1 { U16CString::from_os_str_truncate(windows_path.as_ref().as_os_str()); let encoded_linux_path = U16CString::from_str_truncate(linux_path.as_ref().as_str()); let encoded_name = U16CString::from_os_str_truncate(name); + // SAFETY: + // - `self.0.MountFolder` comes from the validated `WSLPluginAPIV1` struct provided by WSL. + // The API guarantees that this function pointer is non-null for supported versions. + // - All `U16CString` instances (`encoded_windows_path`, `encoded_linux_path`, `encoded_name`) + // ensure null-termination and valid UTF-16 encoding, so the raw pointers passed to the FFI + // are valid for the duration of the call. + // - `session.id()` returns a valid `WSLSessionId` provided by WSL; it remains valid while the + // session is active. + // - No aliasing or mutation of memory occurs while the function pointer is called. + // + // The only `unsafe` operation is the FFI call, which is trusted because it is executed under + // WSL's documented plugin API contract. let result = unsafe { self.0.MountFolder.unwrap_unchecked()( session.id(), encoded_windows_path.as_ptr(), encoded_linux_path.as_ptr(), - read_only as i32, + i32::from(read_only), encoded_name.as_ptr(), ) }; @@ -129,7 +152,7 @@ impl ApiV1 { /// - **Standard Output**: Data output by the process will be readable from the stream. /// /// # Errors - /// This method can return the following a [windows_core::Error]: If the underlying Windows API call fails. + /// This method can return the following a [`windows_core::Error`]: If the underlying Windows API call fails. /// /// # Example /// ```rust,ignore @@ -144,6 +167,7 @@ impl ApiV1 { /// ``` #[cfg_attr(feature = "log-instrument", instrument)] #[doc(alias = "ExecuteBinary")] + #[inline] pub fn execute_binary>( &self, session: &WSLSessionInformation, @@ -164,11 +188,26 @@ impl ApiV1 { .collect(); let mut args_ptrs: Vec<*const u8> = c_args .iter() - .map(|arg| arg.as_ptr() as *const u8) - .chain(once(std::ptr::null::())) + .map(|arg| arg.as_ptr().cast::()) + .chain(once(ptr::null::())) .collect(); let args_ptr = args_ptrs.as_mut_ptr(); let mut socket = MaybeUninit::::uninit(); + // SAFETY: + // - `ExecuteBinaryInDistribution` is guaranteed to be non-null because we first checked + // the API version (>= 2.1.2) before calling `unwrap_unchecked()`. + // - `session.id()` returns a valid integer identifying a WSLSessionInformation`, which comes from + // a live session reference owned by the caller. + // - `path_ptr` points to a null-terminated buffer (`c_path`) that is kept alive for the + // duration of the call. + // - `args_ptr` points to a null-terminated array of pointers (`args_ptrs`), each pointing + // to a valid C string buffer (`c_args`). All these buffers live until after the call. + // - `socket.as_mut_ptr()` is a valid, writable pointer to uninitialized memory. The + // function is documented to write a valid SOCKET there on success. + // - We only call `assume_init()` if `HRESULT::ok()` reports success, ensuring the socket + // field has been properly initialized by the callee. + // - `TcpStream::from_raw_socket` takes ownership of the returned socket; no double-close + // occurs because we never manually close it. let stream = unsafe { HRESULT(self.0.ExecuteBinary.unwrap_unchecked()( session.id(), @@ -187,7 +226,11 @@ impl ApiV1 { #[cfg_attr(feature = "log-instrument", instrument)] pub(crate) fn plugin_error(&self, error: &OsStr) -> WinResult<()> { let error_utf16 = U16CString::from_os_str_truncate(error); - HRESULT(unsafe { self.0.PluginError.unwrap_unchecked()(error_utf16.as_ptr()) }).ok() + HRESULT( + // SAFETY: We know the pointer is always valid if the API ref is valid + unsafe { self.0.PluginError.unwrap_unchecked()(error_utf16.as_ptr()) }, + ) + .ok() } /// Execute a program in a user distribution @@ -223,6 +266,7 @@ impl ApiV1 { /// ``` #[doc(alias = "ExecuteBinaryInDistribution")] #[cfg_attr(feature = "log-instrument", instrument)] + #[inline] pub fn execute_binary_in_distribution>( &self, session: &WSLSessionInformation, @@ -246,16 +290,33 @@ impl ApiV1 { .collect(); let mut args_ptrs: Vec<_> = c_args .iter() - .map(|arg| arg.as_ptr() as *const u8) - .chain(once(std::ptr::null())) + .map(|arg| arg.as_ptr().cast::()) + .chain(once(ptr::null())) .collect(); let args_ptr = args_ptrs.as_mut_ptr(); let mut socket = MaybeUninit::::uninit(); + // SAFETY: + // - `ExecuteBinaryInDistribution` is guaranteed to be non-null because we first checked + // the API version (>= 2.1.2) before calling `unwrap_unchecked()`. + // - `session.id()` returns a valid integer identifying a WSLSessionInformation`, which comes from + // a live session reference owned by the caller. + // - `distribution_id` is passed by reference as a properly aligned and sized GUID, cast + // to the expected FFI type. + // - `path_ptr` points to a null-terminated buffer (`c_path`) that is kept alive for the + // duration of the call. + // - `args_ptr` points to a null-terminated array of pointers (`args_ptrs`), each pointing + // to a valid C string buffer (`c_args`). All these buffers live until after the call. + // - `socket.as_mut_ptr()` is a valid, writable pointer to uninitialized memory. The + // function is documented to write a valid SOCKET there on success. + // - We only call `assume_init()` if `HRESULT::ok()` reports success, ensuring the socket + // field has been properly initialized by the callee. + // - `TcpStream::from_raw_socket` takes ownership of the returned socket; no double-close + // occurs because we never manually close it. + #[allow(clippy::absolute_paths)] let stream = unsafe { HRESULT(self.0.ExecuteBinaryInDistribution.unwrap_unchecked()( session.id(), - (&distribution_id) as *const GUID - as *const wslpluginapi_sys::windows_sys::core::GUID, + (&raw const distribution_id).cast::(), path_ptr, args_ptr, socket.as_mut_ptr(), @@ -273,7 +334,8 @@ impl ApiV1 { } impl Debug for ApiV1 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ApiV1") .field("version", self.version()) .finish() diff --git a/wslplugins-rs/src/api/errors.rs b/wslplugins-rs/src/api/errors.rs index eb21e36..66a771b 100644 --- a/wslplugins-rs/src/api/errors.rs +++ b/wslplugins-rs/src/api/errors.rs @@ -34,6 +34,7 @@ impl From for HRESULT { /// /// # Returns /// An `HRESULT` code representing the error. + #[inline] fn from(value: Error) -> Self { match value { Error::RequiresUpdate(err) => err.into(), @@ -50,6 +51,7 @@ impl From for WinError { /// /// # Returns /// A `WinError` representing the error. + #[inline] fn from(value: Error) -> Self { match value { Error::RequiresUpdate { .. } => HRESULT(WSL_E_PLUGIN_REQUIRES_UPDATE).into(), diff --git a/wslplugins-rs/src/api/errors/require_update_error.rs b/wslplugins-rs/src/api/errors/require_update_error.rs index e8ea012..8d24eb1 100644 --- a/wslplugins-rs/src/api/errors/require_update_error.rs +++ b/wslplugins-rs/src/api/errors/require_update_error.rs @@ -35,7 +35,9 @@ impl Error { /// /// # Returns /// A new instance of `Error`. - pub fn new(current_version: WSLVersion, required_version: WSLVersion) -> Self { + #[must_use] + #[inline] + pub const fn new(current_version: WSLVersion, required_version: WSLVersion) -> Self { Self { current_version, required_version, @@ -44,14 +46,15 @@ impl Error { } impl From for HRESULT { - /// Converts the `Error` into an `HRESULT` error code. + /// Converts the `Error` into the corresponding `HRESULT` error code. /// /// This implementation maps the custom `Error` to the `WSL_E_PLUGIN_REQUIRES_UPDATE` HRESULT. /// - /// # Returns - /// - `[WSL_E_PLUGIN_REQUIRES_UPDATE]: Indicates the WSL version is insufficient for the plugin. + /// # Note + /// [`WSL_E_PLUGIN_REQUIRES_UPDATE`]: Indicates the WSL version is insufficient for the plugin. + #[inline] fn from(_: Error) -> Self { - HRESULT(WSL_E_PLUGIN_REQUIRES_UPDATE) + Self(WSL_E_PLUGIN_REQUIRES_UPDATE) } } diff --git a/wslplugins-rs/src/api/utils.rs b/wslplugins-rs/src/api/utils.rs index fe2acee..77a98d5 100644 --- a/wslplugins-rs/src/api/utils.rs +++ b/wslplugins-rs/src/api/utils.rs @@ -25,12 +25,10 @@ pub(crate) fn check_required_version_result_from_context( wsl_context: Option<&WSLContext>, required_version: &WSLVersion, ) -> Result<()> { - if let Some(context) = wsl_context { + wsl_context.map_or(Ok(()), |context| { let current_version = context.api.version(); check_required_version_result(current_version, required_version) - } else { - Ok(()) - } + }) } #[cfg(test)] diff --git a/wslplugins-rs/src/core_distribution_information.rs b/wslplugins-rs/src/core_distribution_information.rs index 2fbd479..b120840 100644 --- a/wslplugins-rs/src/core_distribution_information.rs +++ b/wslplugins-rs/src/core_distribution_information.rs @@ -29,7 +29,7 @@ pub trait CoreDistributionInformation { /// Retrieves the name of the distribution. /// /// # Returns - /// An [OsString] containing the display name of the distribution. + /// An [`OsString`] containing the display name of the distribution. fn name(&self) -> OsString; /// Retrieves the package family name of the distribution, if available. diff --git a/wslplugins-rs/src/cstring_ext.rs b/wslplugins-rs/src/cstring_ext.rs index ed6fa56..4e0f44b 100644 --- a/wslplugins-rs/src/cstring_ext.rs +++ b/wslplugins-rs/src/cstring_ext.rs @@ -1,17 +1,18 @@ use std::ffi::CString; -pub(crate) trait CstringExt { +pub trait CstringExt { /// Creates a `CString` from a string slice, truncating at the first null byte if present. fn from_str_truncate(value: &str) -> Self; } impl CstringExt for CString { + #[expect(clippy::indexing_slicing, reason = "The slice is controlled")] fn from_str_truncate(value: &str) -> Self { let bytes = value.as_bytes(); - let truncated_bytes = match bytes.iter().position(|&b| b == 0) { - Some(pos) => &bytes[..pos], - None => bytes, - }; + let truncated_bytes = bytes + .iter() + .position(|&b| b == 0) + .map_or(bytes, |pos| &bytes[..pos]); // SAFETY: `truncated_bytes` is guaranteed not to contain null bytes. unsafe { Self::from_vec_unchecked(truncated_bytes.to_vec()) } } diff --git a/wslplugins-rs/src/distribution_information.rs b/wslplugins-rs/src/distribution_information.rs index 3ad801f..284fb69 100644 --- a/wslplugins-rs/src/distribution_information.rs +++ b/wslplugins-rs/src/distribution_information.rs @@ -21,10 +21,10 @@ use crate::core_distribution_information::CoreDistributionInformation; use crate::WSLContext; use crate::WSLVersion; use std::ffi::OsString; -use std::fmt::{Debug, Display}; -use std::hash::Hash; -use std::mem; -use std::os::windows::ffi::OsStringExt; +use std::fmt::{self, Debug, Display}; +use std::hash::{Hash, Hasher}; +use std::os::windows::ffi::OsStringExt as _; +use std::{mem, ptr}; use windows_core::{GUID, PCWSTR}; /// Represents detailed information about a WSL distribution. @@ -35,29 +35,31 @@ use windows_core::{GUID, PCWSTR}; pub struct DistributionInformation(wslpluginapi_sys::WSLDistributionInformation); impl AsRef for wslpluginapi_sys::WSLDistributionInformation { + #[inline] fn as_ref(&self) -> &DistributionInformation { - unsafe { - &*(self as *const wslpluginapi_sys::WSLDistributionInformation - as *const DistributionInformation) - } + // SAFETY: conveting this kind of ref is safe as it is transparent + unsafe { &*ptr::from_ref::(self).cast::() } } } impl From for wslpluginapi_sys::WSLDistributionInformation { + #[inline] fn from(value: DistributionInformation) -> Self { value.0 } } impl AsRef for DistributionInformation { + #[inline] fn as_ref(&self) -> &wslpluginapi_sys::WSLDistributionInformation { &self.0 } } impl From for DistributionInformation { + #[inline] fn from(value: wslpluginapi_sys::WSLDistributionInformation) -> Self { - DistributionInformation(value) + Self(value) } } @@ -71,6 +73,7 @@ impl DistributionInformation { /// - `Ok(pid)`: The PID of the init process. /// # Errors /// [Error]: If the runtime version version is insufficient. + #[inline] pub fn init_pid(&self) -> Result { check_required_version_result_from_context( WSLContext::get_current(), @@ -84,21 +87,29 @@ impl DistributionInformation { /// # Returns /// The PID namespace as a `u64`. /// - pub fn pid_namespace(&self) -> u64 { + #[inline] + #[must_use] + pub const fn pid_namespace(&self) -> u64 { self.0.PidNamespace } } impl CoreDistributionInformation for DistributionInformation { + #[inline] fn id(&self) -> GUID { + // SAFETY: Id is known to be valid GUID and windows_sys GUID and windows_core GUID has same representation unsafe { mem::transmute_copy(&self.0.Id) } } + #[inline] fn name(&self) -> OsString { + // SAFETY: Name is known to be valid unsafe { OsString::from_wide(PCWSTR::from_raw(self.0.Name).as_wide()) } } + #[inline] fn package_family_name(&self) -> Option { + // SAFETY: check already inside unsafe { let ptr = self.0.PackageFamilyName; if ptr.is_null() { @@ -109,11 +120,13 @@ impl CoreDistributionInformation for DistributionInformation { } } + #[inline] fn flavor(&self) -> Result> { check_required_version_result_from_context( WSLContext::get_current(), &WSLVersion::new(2, 4, 4), )?; + // SAFETY: check already inside and before by versionning unsafe { let ptr = self.0.Flavor; if ptr.is_null() { @@ -124,11 +137,13 @@ impl CoreDistributionInformation for DistributionInformation { } } + #[inline] fn version(&self) -> Result> { check_required_version_result_from_context( WSLContext::get_current(), &WSLVersion::new(2, 4, 4), )?; + // SAFETY: check did before by versionning. unsafe { let ptr = self.0.Flavor; if ptr.is_null() { @@ -145,6 +160,7 @@ where T: CoreDistributionInformation, { /// Compares two distributions for equality based on their IDs. + #[inline] fn eq(&self, other: &T) -> bool { self.id() == other.id() } @@ -152,13 +168,17 @@ where impl Hash for DistributionInformation { /// Computes a hash based on the distribution's ID. - fn hash(&self, state: &mut H) { + #[inline] + fn hash(&self, state: &mut H) { self.id().hash(state); } } impl Display for DistributionInformation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + #[allow(clippy::use_debug, reason = "GUID display")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // SAFETY: Name is known to be valid unsafe { write!( f, @@ -171,7 +191,8 @@ impl Display for DistributionInformation { } impl Debug for DistributionInformation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut dbg = f.debug_struct("DistributionInformation"); dbg.field("name", &self.name()) .field("id", &self.id()) @@ -183,17 +204,17 @@ impl Debug for DistributionInformation { dbg.field("init_pid", &pid); } else { exhaustive = false; - }; + } if let Ok(flavor) = self.flavor() { dbg.field("flavor", &flavor); } else { exhaustive = false; - }; + } if let Ok(version) = self.version() { dbg.field("version", &version); } else { exhaustive = false; - }; + } if exhaustive { dbg.finish() diff --git a/wslplugins-rs/src/lib.rs b/wslplugins-rs/src/lib.rs index 370a4ed..4a7da1b 100644 --- a/wslplugins-rs/src/lib.rs +++ b/wslplugins-rs/src/lib.rs @@ -1,4 +1,3 @@ -#![warn(missing_docs)] // Enable doc_cfg if docrs #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -42,18 +41,18 @@ pub mod api; // Internal modules for managing specific WSL features. mod core_distribution_information; pub(crate) mod cstring_ext; -pub extern crate windows_core; +pub use windows_core; mod distribution_information; mod offline_distribution_information; mod utils; mod wsl_context; mod wsl_session_information; -pub mod wsl_user_configuration; -pub use wsl_user_configuration::WSLUserConfiguration; mod wsl_vm_creation_settings; #[cfg(doc)] use crate::plugin::WSLPluginV1; -pub extern crate typed_path; +pub mod wsl_user_configuration; +pub use typed_path; +pub use wsl_user_configuration::WSLUserConfiguration; /// Tools and utilities for creating custom WSL plugins. pub mod plugin; @@ -68,7 +67,7 @@ mod wsl_version; pub use wsl_version::WSLVersion; /// Re-exports procedural macros when the `macro` feature is enabled. -/// It allow to mark a plugin struct (that implement [WSLPluginV1] trait) to be easely integrated to the WSL plugin system without writing manually C code for entry point or hooks. +/// It allow to mark a plugin struct (that implement [`WSLPluginV1`] trait) to be easely integrated to the WSL plugin system without writing manually C code for entry point or hooks. #[cfg(feature = "macro")] pub use wslplugins_macro::wsl_plugin_v1; diff --git a/wslplugins-rs/src/offline_distribution_information.rs b/wslplugins-rs/src/offline_distribution_information.rs index c128ae8..13a2865 100644 --- a/wslplugins-rs/src/offline_distribution_information.rs +++ b/wslplugins-rs/src/offline_distribution_information.rs @@ -3,7 +3,6 @@ //! This module provides an abstraction over `WslOfflineDistributionInformation` from the WSL Plugin API, //! offering a safe and idiomatic Rust interface for accessing offline distribution details. -extern crate wslpluginapi_sys; use crate::{ api::{ errors::require_update_error::Result, utils::check_required_version_result_from_context, @@ -13,10 +12,11 @@ use crate::{ }; use std::{ ffi::OsString, - fmt::{Debug, Display}, - hash::Hash, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, mem, - os::windows::ffi::OsStringExt, + os::windows::ffi::OsStringExt as _, + ptr, }; use windows_core::{GUID, PCWSTR}; @@ -28,40 +28,46 @@ use windows_core::{GUID, PCWSTR}; pub struct OfflineDistributionInformation(wslpluginapi_sys::WslOfflineDistributionInformation); impl From for wslpluginapi_sys::WslOfflineDistributionInformation { + #[inline] fn from(value: OfflineDistributionInformation) -> Self { value.0 } } impl From for OfflineDistributionInformation { + #[inline] fn from(value: wslpluginapi_sys::WslOfflineDistributionInformation) -> Self { - OfflineDistributionInformation(value) + Self(value) } } impl AsRef for OfflineDistributionInformation { + #[inline] fn as_ref(&self) -> &wslpluginapi_sys::WslOfflineDistributionInformation { &self.0 } } impl AsRef for wslpluginapi_sys::WslOfflineDistributionInformation { + #[inline] fn as_ref(&self) -> &OfflineDistributionInformation { - unsafe { - &*(self as *const wslpluginapi_sys::WslOfflineDistributionInformation - as *const OfflineDistributionInformation) - } + // SAFETY: This conversion is safe because of transparency. + unsafe { &*ptr::from_ref::(self).cast::() } } } impl CoreDistributionInformation for OfflineDistributionInformation { /// Retrieves the [GUID] of the offline distribution. + #[inline] fn id(&self) -> GUID { + // SAFETY: Id is known to be valid GUID and windows_sys GUID and windows_core GUID has same representation unsafe { mem::transmute_copy(&self.0.Id) } } - /// Retrieves the name of the offline distribution as an [OsString]. + /// Retrieves the name of the offline distribution as an [`OsString`]. + #[inline] fn name(&self) -> OsString { + // SAFETY: name is known to be valid unsafe { OsString::from_wide(PCWSTR::from_raw(self.0.Name).as_wide()) } } @@ -70,7 +76,9 @@ impl CoreDistributionInformation for OfflineDistributionInformation { /// # Returns /// - `Some(OsString)`: If the package family name is set. /// - `None`: If the package family name is null or empty. + #[inline] fn package_family_name(&self) -> Option { + // SAFETY: check already inside unsafe { let ptr = PCWSTR::from_raw(self.0.PackageFamilyName); if ptr.is_null() || ptr.is_empty() { @@ -81,11 +89,14 @@ impl CoreDistributionInformation for OfflineDistributionInformation { } } + #[inline] fn flavor(&self) -> Result> { check_required_version_result_from_context( WSLContext::get_current(), &WSLVersion::new(2, 4, 4), )?; + + // SAFETY: check already inside unsafe { let ptr = PCWSTR::from_raw(self.0.Flavor); if ptr.is_null() || ptr.is_empty() { @@ -96,11 +107,13 @@ impl CoreDistributionInformation for OfflineDistributionInformation { } } + #[inline] fn version(&self) -> Result> { check_required_version_result_from_context( WSLContext::get_current(), &WSLVersion::new(2, 4, 4), )?; + // SAFETY: check already inside unsafe { let ptr = PCWSTR::from_raw(self.0.Flavor); if ptr.is_null() || ptr.is_empty() { @@ -117,6 +130,7 @@ where T: CoreDistributionInformation, { /// Compares two distributions by their IDs for equality. + #[inline] fn eq(&self, other: &T) -> bool { self.id() == other.id() } @@ -124,13 +138,17 @@ where impl Hash for OfflineDistributionInformation { /// Computes a hash based on the distribution's ID. - fn hash(&self, state: &mut H) { + #[inline] + fn hash(&self, state: &mut H) { self.id().hash(state); } } impl Display for OfflineDistributionInformation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + #[expect(clippy::use_debug, reason = "GUID display")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // SAFETY: Name is known to be valid unsafe { write!( f, @@ -143,6 +161,7 @@ impl Display for OfflineDistributionInformation { } impl Debug for OfflineDistributionInformation { + #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut dbg = f.debug_struct("DistributionInformation"); dbg.field("name", &self.name()) diff --git a/wslplugins-rs/src/plugin/error.rs b/wslplugins-rs/src/plugin/error.rs index 44a8ece..5a444c8 100644 --- a/wslplugins-rs/src/plugin/error.rs +++ b/wslplugins-rs/src/plugin/error.rs @@ -7,6 +7,7 @@ use crate::WSLContext; #[cfg(feature = "log")] use log::debug; +use std::borrow::ToOwned; use std::ffi::{OsStr, OsString}; use std::num::NonZeroI32; use thiserror::Error; @@ -35,6 +36,7 @@ impl std::fmt::Display for Error { /// /// If an error message is present, it is included in the output. /// Otherwise, only the error code is displayed. + #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.message { Some(message) => write!( @@ -57,17 +59,20 @@ impl Error { /// /// # Returns /// A new instance of `Error`. + #[must_use] + #[inline] pub fn new(code: HRESULT, message: Option<&OsStr>) -> Self { let code = if code.is_ok() { WinError::from_hresult(code).code() } else { code }; + // SAFETY: the code getted from WinError is guaranteed to be valid let code = unsafe { NonZeroI32::new_unchecked(code.0) }; Self { code, - message: message.map(|m| m.to_owned()), + message: message.map(ToOwned::to_owned), } } @@ -78,6 +83,8 @@ impl Error { /// /// # Returns /// A new instance of `Error` without an associated message. + #[must_use] + #[inline] pub fn with_code(code: HRESULT) -> Self { Self::new(code, None) } @@ -90,6 +97,8 @@ impl Error { /// /// # Returns /// A new instance of `Error`. + #[must_use] + #[inline] pub fn with_message(code: HRESULT, message: &OsStr) -> Self { Self::new(code, Some(message)) } @@ -98,7 +107,8 @@ impl Error { /// /// # Returns /// The error code wrapped in an `HRESULT`. - pub fn code(&self) -> HRESULT { + #[inline] + pub const fn code(&self) -> HRESULT { HRESULT(self.code.get()) } @@ -106,6 +116,8 @@ impl Error { /// /// # Returns /// A reference to the error message, if present. + #[must_use] + #[inline] pub fn message(&self) -> Option<&OsStr> { self.message.as_deref() } @@ -120,14 +132,14 @@ impl Error { pub(crate) fn consume_error_message_unwrap>(self) -> R { if let Some(ref mess) = self.message { if let Some(context) = WSLContext::get_current() { - let _plugin_error_result = context.api.plugin_error(mess.as_os_str()); + let plugin_error_result = context.api.plugin_error(mess.as_os_str()); #[cfg(feature = "log")] - if let Err(err) = _plugin_error_result { + if let Err(err) = plugin_error_result { debug!( "Unable to set plugin error message {} due to error: {}", mess.to_string_lossy(), err - ) + ); } } } @@ -140,14 +152,16 @@ impl From for WinError { /// /// # Returns /// A `WinError` constructed from the error's code and message. + #[inline] fn from(value: Error) -> Self { - match value.message { - Some(ref message) => { + let code = value.code(); + value.message.as_ref().map_or_else( + || Self::from_hresult(code), + |message| { let msg_string = message.to_string_lossy(); - WinError::new(value.code(), &msg_string) - } - None => WinError::from_hresult(value.code()), - } + Self::new(code, &msg_string) + }, + ) } } @@ -156,14 +170,16 @@ impl From for Error { /// /// # Returns /// An `Error` containing the code and message from the `WinError`. + #[inline] fn from(value: WinError) -> Self { - let os_message = if !value.message().is_empty() { - Some(OsString::from(value.message())) - } else { + let os_message = if value.message().is_empty() { None + } else { + Some(OsString::from(value.message())) }; Self { + // SAFETY: As we have a valid WinError, we can safely extract the code. code: unsafe { NonZeroI32::new_unchecked(value.code().0) }, message: os_message, } @@ -175,6 +191,7 @@ impl From for Error { /// /// # Returns /// An `Error` containing the `HRESULT` as its code. + #[inline] fn from(value: HRESULT) -> Self { Self::new(value, None) } diff --git a/wslplugins-rs/src/plugin/utils.rs b/wslplugins-rs/src/plugin/utils.rs index d9509af..6d8e031 100644 --- a/wslplugins-rs/src/plugin/utils.rs +++ b/wslplugins-rs/src/plugin/utils.rs @@ -18,7 +18,7 @@ use super::{Result, WSLPluginV1}; /// creating a new plugin instance. If the API version does not meet the requirements, an error is returned. /// /// # Type Parameters -/// - `T`: A type implementing the [WSLPluginV1] trait, representing the plugin to create. +/// - `T`: A type implementing the [`WSLPluginV1`] trait, representing the plugin to create. /// /// # Arguments /// - `api`: A reference to the WSL Plugin API version 1 structure. @@ -31,18 +31,16 @@ use super::{Result, WSLPluginV1}; /// - `Err(WinError)`: If the API version is insufficient or the plugin is already initialized. /// /// # Errors -/// - Returns [WinError]`::from(`[ERROR_ALREADY_INITIALIZED]`)` if a plugin is already initialized. -/// - Returns [WinError]`::from(`[WSL_E_PLUGIN_REQUIRES_UPDATE](wslpluginapi_sys::WSL_E_PLUGIN_REQUIRES_UPDATE)`)` error if the API version is insufficient. -/// -/// # Safety -/// This function calls an unsafe API to check the reqred version. Ensure the provided API pointer -/// is valid and correctly initialized. +/// - Returns [WinError]::from([ERROR_ALREADY_INITIALIZED]) if a plugin is already initialized. +/// - Returns [WinError]::from([WSL_E_PLUGIN_REQUIRES_UPDATE](wslpluginapi_sys::WSL_E_PLUGIN_REQUIRES_UPDATE)) error if the API version is insufficient. +#[inline] pub fn create_plugin_with_required_version( api: &'static WSLPluginAPIV1, required_major: u32, required_minor: u32, required_revision: u32, ) -> WinResult { + // SAFETY: As api reference exist it's safe to call it unsafe { HRESULT(wslpluginapi_sys::require_version( required_major, @@ -51,19 +49,18 @@ pub fn create_plugin_with_required_version( api, )) .ok()?; - } + }; WSLContext::init(api.as_ref()) - .ok_or(WinError::from_hresult(HRESULT::from_win32( - ERROR_ALREADY_INITIALIZED, - ))) + .ok_or_else(|| WinError::from_hresult(HRESULT::from_win32(ERROR_ALREADY_INITIALIZED))) .and_then(T::try_new) } +#[expect(clippy::missing_errors_doc)] /// Converts a generic `Result` using the custom `Error` type into a `WinResult`. /// /// This function simplifies the interoperability between the custom error handling /// in the WSL plugin system and the Windows error system by mapping the plugin [Error] -/// into a [windows_core::Error] using the `consume_error_message_unwrap` method. +/// into a [`windows_core::Error`] using the `consume_error_message_unwrap` method. /// /// # Arguments /// - `result`: A [`Result`] using the custom [Error] type defined in this crate. @@ -71,17 +68,18 @@ pub fn create_plugin_with_required_version( /// # Returns /// A `WinResult` where: /// - `Ok(value)` contains the successful result `T`. -/// - `Err(error)` contains a [windows_core::Error] converted from the plugin [Error]. +/// - `Err(error)` contains a [`windows_core::Error`] converted from the plugin [Error]. /// /// # Behavior /// - If the `result` is `Ok`, it is returned as-is. /// - If the `result` is `Err`, the error is consumed /// and sent to WSL and is then -/// converted into a [windows_core::Error]. +/// converted into a [`windows_core::Error`]. /// /// # Usage /// This utility is intended to facilitate the transition between idiomatic Rust /// error handling and the Windows API error-handling conventions. +#[inline] pub fn consume_to_win_result(result: Result) -> WinResult { - result.map_err(|err| err.consume_error_message_unwrap()) + result.map_err(super::error::Error::consume_error_message_unwrap) } diff --git a/wslplugins-rs/src/plugin/wsl_plugin_v1.rs b/wslplugins-rs/src/plugin/wsl_plugin_v1.rs index e6336ac..0d492c5 100644 --- a/wslplugins-rs/src/plugin/wsl_plugin_v1.rs +++ b/wslplugins-rs/src/plugin/wsl_plugin_v1.rs @@ -4,6 +4,8 @@ //! synchronous notifications sent to a WSL plugin. The trait defines lifecycle events //! for managing the state of the WSL VM, distributions, and related settings. +#[cfg(doc)] +use super::error::Error; use super::error::Result; use crate::{ distribution_information::DistributionInformation, @@ -12,6 +14,8 @@ use crate::{ wsl_vm_creation_settings::WSLVmCreationSettings, WSLContext, }; use std::marker::Sized; +#[cfg(doc)] +use windows_core::Error as WinError; use windows_core::Result as WinResult; /// Trait defining synchronous notifications sent to the plugin. @@ -51,9 +55,8 @@ pub trait WSLPluginV1: Sized + Sync { /// # Arguments /// - `context`: A reference to the `WSLContext` providing access to the plugin API. /// - /// # Returns - /// - `Ok(Self)`: If the plugin was successfully initialized. - /// - `Err(WinError)`: If initialization fails. + /// # Errors + /// - [`WinError`]: If initialization fails. fn try_new(context: &'static WSLContext) -> WinResult; /// Called when the VM has started. @@ -62,10 +65,13 @@ pub trait WSLPluginV1: Sized + Sync { /// - `session`: Information about the current session. /// - `user_settings`: Custom user settings for the VM creation. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(`Error`)`: If the event handling failed. - #[allow(unused_variables)] + /// # Errors + /// - [`Error`]: If the event handling failed. + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just Ok(())" + )] + #[inline] fn on_vm_started( &self, session: &WSLSessionInformation, @@ -79,10 +85,13 @@ pub trait WSLPluginV1: Sized + Sync { /// # Arguments /// - `session`: Information about the current session. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(WinError)`: If the event handling failed. - #[allow(unused_variables)] + /// # Errors + /// - `Errors`: If the event handling failed. + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just Ok(())" + )] + #[inline] fn on_vm_stopping(&self, session: &WSLSessionInformation) -> WinResult<()> { Ok(()) } @@ -93,10 +102,13 @@ pub trait WSLPluginV1: Sized + Sync { /// - `session`: Information about the current session. /// - `distribution`: Information about the distribution. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(Error)`: If the event handling failed. - #[allow(unused_variables)] + /// # Errors + /// - `Errors`: If the event handling failed. + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just Ok(())" + )] + #[inline] fn on_distribution_started( &self, session: &WSLSessionInformation, @@ -111,13 +123,16 @@ pub trait WSLPluginV1: Sized + Sync { /// - `session`: Information about the current session. /// - `distribution`: Information about the distribution. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(WinError)`: If the event handling failed. + /// # Errors + /// - `WinError`: If the event handling failed. /// /// # Notes /// - This method might be called multiple times for the same distribution if stopping fails. - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just Ok(())" + )] + #[inline] fn on_distribution_stopping( &self, session: &WSLSessionInformation, @@ -132,13 +147,15 @@ pub trait WSLPluginV1: Sized + Sync { /// - `session`: Information about the current session. /// - `distribution`: Offline information about the distribution. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(WinError)`: If the event handling failed. - /// + /// # Errors + /// - `WinError`: If the event handling failed. /// # Notes /// - Introduced in API version 2.1.2. - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just Ok(())" + )] + #[inline] fn on_distribution_registered( &self, session: &WSLSessionInformation, @@ -153,13 +170,17 @@ pub trait WSLPluginV1: Sized + Sync { /// - `session`: Information about the current session. /// - `distribution`: Offline information about the distribution. /// - /// # Returns - /// - `Ok(())`: If the plugin successfully handled the event. - /// - `Err(WinError)`: If the event handling failed. + /// # Errors + /// - `WinError`: If the event handling failed. /// /// # Notes /// - Introduced in API version 2.1.2. - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "We are on a treit with default methods that return just + Ok(())" + )] + #[inline] fn on_distribution_unregistered( &self, session: &WSLSessionInformation, diff --git a/wslplugins-rs/src/wsl_context.rs b/wslplugins-rs/src/wsl_context.rs index 6880da4..0726086 100644 --- a/wslplugins-rs/src/wsl_context.rs +++ b/wslplugins-rs/src/wsl_context.rs @@ -36,10 +36,11 @@ impl WSLContext { /// eprintln!("WSL context is not initialized."); /// } /// ``` - pub fn get_current() -> Option<&'static WSLContext> { + #[inline] + pub fn get_current() -> Option<&'static Self> { CURRENT_CONTEXT.get() } - + #[expect(clippy::expect_used)] /// Retrieves the current `WSLContext` instance or panics if it is not initialized. /// /// # Panics @@ -47,20 +48,23 @@ impl WSLContext { /// /// # Returns /// A reference to the current `WSLContext`. - pub fn get_current_or_panic() -> &'static WSLContext { + #[must_use] + #[inline] + pub fn get_current_or_panic() -> &'static Self { Self::get_current().expect("WSL context is not initialised.") } /// Initializes the global `WSLContext` with the provided `ApiV1` instance. /// /// # Arguments - /// - `api`: The [ApiV1] instance to associate with the context. + /// - `api`: The [`ApiV1`] instance to associate with the context. /// /// # Returns /// - `Some(&'static WSLContext)`: If the context was successfully initialized. /// - `None`: If the context has already been initialized. + #[inline] pub fn init(api: &'static ApiV1) -> Option<&'static Self> { - CURRENT_CONTEXT.set(WSLContext { api }).ok()?; + CURRENT_CONTEXT.set(Self { api }).ok()?; CURRENT_CONTEXT.get() } } diff --git a/wslplugins-rs/src/wsl_session_information.rs b/wslplugins-rs/src/wsl_session_information.rs index a33bb0f..4436a23 100644 --- a/wslplugins-rs/src/wsl_session_information.rs +++ b/wslplugins-rs/src/wsl_session_information.rs @@ -3,7 +3,6 @@ //! This module provides a safe abstraction over the `WSLSessionInformation` structure //! from the WSL Plugin API, allowing access to session details in an idiomatic Rust interface. -extern crate wslpluginapi_sys; use core::hash; use std::{fmt, os::windows::raw::HANDLE}; use wslpluginapi_sys::windows_sys::Win32::Security::PSID; @@ -19,7 +18,9 @@ impl WSLSessionInformation { /// /// # Returns /// The unique session ID as a [u32]. - pub fn id(&self) -> u32 { + #[must_use] + #[inline] + pub const fn id(&self) -> u32 { self.0.SessionId } @@ -30,7 +31,9 @@ impl WSLSessionInformation { /// # Safety /// This function returns a raw handle to the user token. /// The handle should be used only during the life of the session and must not be closed - pub unsafe fn user_token(&self) -> HANDLE { + #[must_use] + #[inline] + pub const unsafe fn user_token(&self) -> HANDLE { self.0.UserToken } @@ -41,33 +44,39 @@ impl WSLSessionInformation { /// # Safety /// This function returns a raw pointer to the user SID. /// This pointer should be used only during the life of the session and must not be freed or modified. - pub unsafe fn user_sid(&self) -> PSID { + #[must_use] + #[inline] + pub const unsafe fn user_sid(&self) -> PSID { self.0.UserSid } } impl From for WSLSessionInformation { + #[inline] fn from(value: wslpluginapi_sys::WSLSessionInformation) -> Self { - WSLSessionInformation(value) + Self(value) } } impl From for wslpluginapi_sys::WSLSessionInformation { + #[inline] fn from(value: WSLSessionInformation) -> Self { value.0 } } impl AsRef for wslpluginapi_sys::WSLSessionInformation { + #[inline] fn as_ref(&self) -> &WSLSessionInformation { + // SAFETY: conveting this kind of ref is safe as it is transparent unsafe { - &*(self as *const wslpluginapi_sys::WSLSessionInformation - as *const WSLSessionInformation) + &*std::ptr::from_ref::(self).cast::() } } } impl AsRef for WSLSessionInformation { + #[inline] fn as_ref(&self) -> &wslpluginapi_sys::WSLSessionInformation { &self.0 } @@ -78,6 +87,7 @@ impl hash::Hash for WSLSessionInformation { /// /// # Arguments /// - `state`: The hasher state to update with the session ID. + #[inline] fn hash(&self, state: &mut H) { self.0.SessionId.hash(state); } @@ -91,6 +101,7 @@ impl PartialEq for WSLSessionInformation { /// /// # Returns /// `true` if the session IDs are equal, `false` otherwise. + #[inline] fn eq(&self, other: &Self) -> bool { self.0.SessionId == other.0.SessionId } @@ -101,6 +112,7 @@ impl fmt::Debug for WSLSessionInformation { /// Formats the session information for debugging. /// /// The output includes the session ID, user token, and user SID. + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WSLSessionInformation") .field("sessionId", &self.0.SessionId) diff --git a/wslplugins-rs/src/wsl_user_configuration/bitflags.rs b/wslplugins-rs/src/wsl_user_configuration/bitflags.rs index 9aa961c..82fe883 100644 --- a/wslplugins-rs/src/wsl_user_configuration/bitflags.rs +++ b/wslplugins-rs/src/wsl_user_configuration/bitflags.rs @@ -1,4 +1,4 @@ -//! Provides a [mod@bitflags] implementation for [WSLUserConfiguration] flags. +//! Provides a [mod@bitflags] implementation for [`WSLUserConfiguration`] flags. use super::WSLUserConfiguration; use bitflags::bitflags; @@ -28,20 +28,23 @@ bitflags! { } impl From for WSLUserConfigurationFlags { + #[inline] fn from(value: WSLUserConfiguration) -> Self { - WSLUserConfigurationFlags::from_bits_truncate(value.0) + Self::from_bits_truncate(value.0) } } impl From for WSLUserConfiguration { + #[inline] fn from(value: WSLUserConfigurationFlags) -> Self { value.bits().into() } } impl Default for WSLUserConfigurationFlags { + #[inline] fn default() -> Self { - WSLUserConfigurationFlags::empty() + Self::empty() } } diff --git a/wslplugins-rs/src/wsl_user_configuration/mod.rs b/wslplugins-rs/src/wsl_user_configuration/mod.rs index adcb7c7..bb16964 100644 --- a/wslplugins-rs/src/wsl_user_configuration/mod.rs +++ b/wslplugins-rs/src/wsl_user_configuration/mod.rs @@ -10,9 +10,9 @@ //! for additional details on WSL user configurations. //! # Features //! -//! - **`bitflags`**: Provides a [bitflags]-based implementation for handling user configuration flags. -//! - **`enumflags2`**: Provides an [enumflags2]-based implementation for handling user configuration flags. -//! - **`flagset`**: Provides a [flagset]-based implementation for handling user configuration flags. +//! - **`bitflags`**: Provides a [`bitflags`]-based implementation for handling user configuration flags. +//! - **`enumflags2`**: Provides an [`enumflags2`]-based implementation for handling user configuration flags. +//! - **`flagset`**: Provides a [`flagset`]-based implementation for handling user configuration flags. #[cfg(feature = "bitflags")] pub mod bitflags; @@ -31,12 +31,14 @@ pub mod flagset; pub struct WSLUserConfiguration(i32); impl From for WSLUserConfiguration { + #[inline] fn from(value: i32) -> Self { Self(value) } } impl From for i32 { + #[inline] fn from(value: WSLUserConfiguration) -> Self { value.0 } diff --git a/wslplugins-rs/src/wsl_version.rs b/wslplugins-rs/src/wsl_version.rs index bbc53d8..dbdb590 100644 --- a/wslplugins-rs/src/wsl_version.rs +++ b/wslplugins-rs/src/wsl_version.rs @@ -1,6 +1,7 @@ use std::{ - fmt::{Debug, Display}, + fmt::{self, Debug, Display}, hash::Hash, + ptr, }; /// Represents a WSL version number. @@ -16,7 +17,7 @@ use std::{ /// assert_eq!(version.revision(), 0); /// ``` #[repr(transparent)] -#[derive(Clone, Eq)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WSLVersion(wslpluginapi_sys::WSLVersion); impl WSLVersion { @@ -27,110 +28,103 @@ impl WSLVersion { /// - `revision`: The revision number. /// # Returns /// The new `WSLVersion` instance. - pub fn new(major: u32, minor: u32, revision: u32) -> Self { - wslpluginapi_sys::WSLVersion { + #[must_use] + #[inline] + pub const fn new(major: u32, minor: u32, revision: u32) -> Self { + Self(wslpluginapi_sys::WSLVersion { Major: major, Minor: minor, Revision: revision, - } - .into() + }) } /// Retrieves the major version number. - pub fn major(&self) -> u32 { + #[must_use] + #[inline] + pub const fn major(&self) -> u32 { self.0.Major } /// Set the major version number. - pub fn set_major(&mut self, major: u32) { - self.0.Major = major + #[inline] + pub const fn set_major(&mut self, major: u32) { + self.0.Major = major; } /// Retrieves the minor version number. - pub fn minor(&self) -> u32 { + #[must_use] + #[inline] + pub const fn minor(&self) -> u32 { self.0.Minor } /// Set the minor version number. - pub fn set_minor(&mut self, minor: u32) { - self.0.Minor = minor + #[inline] + pub const fn set_minor(&mut self, minor: u32) { + self.0.Minor = minor; } /// Retrieves the revision version number. - pub fn revision(&self) -> u32 { + #[must_use] + #[inline] + pub const fn revision(&self) -> u32 { self.0.Revision } /// Set the revision version number. - pub fn set_revision(&mut self, revision: u32) { - self.0.Revision = revision + #[inline] + pub const fn set_revision(&mut self, revision: u32) { + self.0.Revision = revision; } } impl From for WSLVersion { + #[inline] fn from(value: wslpluginapi_sys::WSLVersion) -> Self { - WSLVersion(value) + Self(value) } } impl From for wslpluginapi_sys::WSLVersion { + #[inline] fn from(value: WSLVersion) -> Self { value.0 } } impl AsRef for wslpluginapi_sys::WSLVersion { + #[inline] fn as_ref(&self) -> &WSLVersion { - unsafe { &*(self as *const wslpluginapi_sys::WSLVersion as *const WSLVersion) } + // SAFETY: conveting this kind of ref is safe as it is transparent + unsafe { &*ptr::from_ref::(self).cast::() } } } impl AsRef for WSLVersion { + #[inline] fn as_ref(&self) -> &wslpluginapi_sys::WSLVersion { &self.0 } } -impl Hash for WSLVersion { - fn hash(&self, state: &mut H) { - self.major().hash(state); - self.minor().hash(state); - self.revision().hash(state); - } -} - impl Default for WSLVersion { + #[inline] fn default() -> Self { Self::new(1, 0, 0) } } -impl PartialEq for WSLVersion { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl PartialOrd for WSLVersion { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for WSLVersion { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} impl Display for WSLVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major(), self.minor(), self.revision()) } } impl Debug for WSLVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(stringify!(WSLVersion)) .field("major", &self.major()) .field("minor", &self.minor()) diff --git a/wslplugins-rs/src/wsl_vm_creation_settings.rs b/wslplugins-rs/src/wsl_vm_creation_settings.rs index fa22145..878c038 100644 --- a/wslplugins-rs/src/wsl_vm_creation_settings.rs +++ b/wslplugins-rs/src/wsl_vm_creation_settings.rs @@ -15,29 +15,31 @@ use crate::WSLUserConfiguration; pub struct WSLVmCreationSettings(wslpluginapi_sys::WSLVmCreationSettings); impl From for WSLVmCreationSettings { + #[inline] fn from(value: wslpluginapi_sys::WSLVmCreationSettings) -> Self { - WSLVmCreationSettings(value) + Self(value) } } impl From for wslpluginapi_sys::WSLVmCreationSettings { + #[inline] fn from(value: WSLVmCreationSettings) -> Self { value.0 } } impl AsRef for WSLVmCreationSettings { + #[inline] fn as_ref(&self) -> &wslpluginapi_sys::WSLVmCreationSettings { &self.0 } } impl AsRef for wslpluginapi_sys::WSLVmCreationSettings { + #[inline] fn as_ref(&self) -> &WSLVmCreationSettings { - unsafe { - &*(self as *const wslpluginapi_sys::WSLVmCreationSettings - as *const WSLVmCreationSettings) - } + // SAFETY: conveting this kind of ref is safe as it is transparent + unsafe { &*std::ptr::from_ref::(self).cast::() } } } @@ -51,6 +53,8 @@ impl WSLVmCreationSettings { /// - **`flagset`**: Uses the [flagset] crate for managing flags. /// - **`enumflags2`**: Uses the [enumflags2] crate for managing flags. /// + #[must_use] + #[inline] pub fn custom_configuration_flags(&self) -> WSLUserConfiguration { WSLUserConfiguration::from(self.0.CustomConfigurationFlags) } @@ -60,6 +64,7 @@ impl Debug for WSLVmCreationSettings { /// Formats the VM creation settings for debugging. /// /// The debug output includes the custom configuration flags. + #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WSLVmCreationSettings") .field(