use super::errors::require_update_error::{Error, RequirementDefinition, Result};
use crate::cstring_ext::CstringExt;
use crate::{WSLContext, WSLVersion, WSLVersionCapability};
use std::ffi::CString;
use std::ptr;
use typed_path::Utf8UnixPath;
pub(crate) const fn check_required_version_result(
current_version: &WSLVersion,
required_version: &WSLVersion,
) -> Result<()> {
if current_version.is_at_least(*required_version) {
Ok(())
} else {
Err(Error {
current_version: *current_version,
requirement: RequirementDefinition::Version(*required_version),
})
}
}
pub(crate) fn check_requirement_result(
current_version: &WSLVersion,
requirement: impl Into<RequirementDefinition>,
) -> Result<()> {
let requirement = requirement.into();
match requirement {
RequirementDefinition::Version(version) => {
check_required_version_result(current_version, &version)
}
RequirementDefinition::Capabilities(_) => {
if current_version.is_at_least(requirement.version()) {
Ok(())
} else {
Err(Error::from_requirement(*current_version, requirement))
}
}
}
}
#[inline]
pub(crate) fn check_capability_result_from_context(
wsl_context: Option<&WSLContext>,
capability: WSLVersionCapability,
) -> Result<()> {
wsl_context.map_or(Ok(()), |context| {
let current_version = context.api.version();
check_requirement_result(current_version, capability)
})
}
#[inline]
pub(super) fn encode_c_path(path: &Utf8UnixPath) -> Vec<u8> {
CString::from_str_truncate(path.as_str()).into_bytes_with_nul()
}
#[allow(clippy::similar_names, reason = "naming is clear")]
pub(super) fn encode_c_argv<I>(args: I) -> (Vec<CString>, Vec<*const u8>)
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let iter = args.into_iter();
let (lower, upper) = iter.size_hint();
let count = upper.unwrap_or(lower);
let mut c_args = Vec::<CString>::with_capacity(count);
let mut argv = Vec::<*const u8>::with_capacity(count + 1);
for arg in iter {
let c = CString::from_str_truncate(arg.as_ref());
argv.push(c.as_ptr().cast::<u8>());
c_args.push(c);
}
argv.push(ptr::null());
(c_args, argv)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::WSLVersion;
use proptest::prelude::*;
fn arb_wsl_version() -> impl Strategy<Value = WSLVersion> {
(any::<u32>(), any::<u32>(), any::<u32>())
.prop_map(|(major, minor, revision)| WSLVersion::new(major, minor, revision))
}
#[test]
fn encode_c_path_appends_nul_terminator() {
let encoded = encode_c_path("/bin/sh".as_ref());
assert_eq!(encoded, b"/bin/sh\0");
}
#[test]
fn encode_c_path_truncates_at_interior_nul() {
let encoded = encode_c_path("/bin\0/sh".as_ref());
assert_eq!(encoded, b"/bin\0");
}
#[test]
fn encode_c_argv_truncates_args_and_null_terminates_argv() {
let (c_args, argv) = encode_c_argv(["sh", "arg\0ignored"]);
let encoded_args: Vec<_> = c_args
.iter()
.map(|arg| arg.as_c_str().to_bytes_with_nul())
.collect();
assert_eq!(encoded_args, [b"sh\0".as_slice(), b"arg\0".as_slice()]);
assert_eq!(argv.len(), 3);
assert_eq!(argv.iter().take_while(|ptr| !ptr.is_null()).count(), 2);
assert!(argv.last().is_some_and(|ptr| ptr.is_null()));
}
proptest! {
#[test]
fn check_required_version_result_matches_version_ordering(
current_version in arb_wsl_version(),
required_version in arb_wsl_version(),
) {
let result = check_required_version_result(¤t_version, &required_version);
prop_assert_eq!(result.is_ok(), current_version.is_at_least(required_version));
}
}
}