use std::path::PathBuf;
use std::ptr::NonNull;
use std::rc::Rc;
use std::time::Duration;
use crate::container::{ContainerBuilder, ContainerOptions};
use crate::image::{ImageInfo, ImagePullOperation, ImagePullOptions};
use crate::storage::VhdOptions;
use crate::{com, raw, strings, Error, Result};
#[derive(Clone)]
pub struct Session {
pub(crate) inner: Rc<SessionInner>,
}
impl std::fmt::Debug for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Session").finish_non_exhaustive()
}
}
pub(crate) struct SessionInner {
pub raw: NonNull<std::ffi::c_void>,
terminate_on_drop: bool,
_com: Option<com::ComGuard>,
}
impl Session {
pub fn builder(name: impl Into<String>, storage_path: impl Into<PathBuf>) -> SessionBuilder {
SessionBuilder {
name: name.into(),
storage_path: storage_path.into(),
cpu_count: None,
memory_mb: None,
timeout_ms: None,
enable_gpu: false,
vhd: None,
terminate_on_drop: false,
}
}
pub(crate) fn raw(&self) -> wslc_sys::WslcSession {
self.inner.raw.as_ptr()
}
pub fn pull_image(&self, options: ImagePullOptions) -> ImagePullOperation<'_> {
ImagePullOperation {
session: self,
options,
progress: None,
}
}
pub fn list_images(&self) -> Result<Vec<ImageInfo>> {
let sdk = raw::sdk()?;
raw::map_result(sdk.list_session_images(self.raw()))?
.into_iter()
.map(ImageInfo::try_from)
.collect::<Result<Vec<_>>>()
}
pub fn delete_image(&self, name_or_id: impl AsRef<str>) -> Result<()> {
let name_or_id = name_or_id.as_ref();
if name_or_id.trim().is_empty() {
return Err(Error::InvalidInput(
"image name or id cannot be empty".to_owned(),
));
}
let sdk = raw::sdk()?;
let name_or_id = strings::cstring(name_or_id, "name_or_id")?;
raw::map_result(sdk.delete_session_image(self.raw(), name_or_id.as_ptr()))
}
pub fn tag_image(
&self,
image: impl AsRef<str>,
repo: impl AsRef<str>,
tag: impl AsRef<str>,
) -> Result<()> {
let image = image.as_ref();
let repo = repo.as_ref();
let tag = tag.as_ref();
if image.is_empty() || repo.is_empty() || tag.is_empty() {
return Err(Error::InvalidInput(
"image, repo, and tag cannot be empty".to_owned(),
));
}
let sdk = raw::sdk()?;
let image = strings::cstring(image, "image")?;
let repo = strings::cstring(repo, "repo")?;
let tag = strings::cstring(tag, "tag")?;
let options = wslc_sys::WslcTagImageOptions {
image: image.as_ptr(),
repo: repo.as_ptr(),
tag: tag.as_ptr(),
};
raw::map_result(sdk.tag_session_image(self.raw(), &options))
}
pub fn container(&self, options: ContainerOptions) -> ContainerBuilder {
ContainerBuilder::new(self.clone(), options)
}
pub fn create_vhd_volume(&self, options: VhdOptions) -> Result<()> {
let sdk = raw::sdk()?;
let name = strings::cstring(&options.name, "vhd name")?;
let (flags, uid, gid) = if let Some((uid, gid)) = options.owner {
(wslc_sys::WSLC_VHD_REQ_FLAG_OWNER, uid, gid)
} else {
(wslc_sys::WSLC_VHD_REQ_FLAG_NONE, 0, 0)
};
let raw_options = wslc_sys::WslcVhdRequirements {
name: name.as_ptr(),
sizeBytes: options.size_bytes,
type_: options.vhd_type.as_raw(),
flags,
uid,
gid,
};
raw::map_result(sdk.create_session_vhd_volume(self.raw(), &raw_options))
}
pub fn delete_vhd_volume(&self, name: impl AsRef<str>) -> Result<()> {
let name = name.as_ref();
if name.trim().is_empty() {
return Err(Error::InvalidInput(
"volume name cannot be empty".to_owned(),
));
}
let sdk = raw::sdk()?;
let name = strings::cstring(name, "volume name")?;
raw::map_result(sdk.delete_session_vhd_volume(self.raw(), name.as_ptr()))
}
pub fn terminate(&self) -> Result<()> {
let sdk = raw::sdk()?;
raw::map_result(sdk.terminate_session(self.raw()))
}
}
pub struct SessionBuilder {
name: String,
storage_path: PathBuf,
cpu_count: Option<u32>,
memory_mb: Option<u32>,
timeout_ms: Option<u32>,
enable_gpu: bool,
vhd: Option<VhdOptions>,
terminate_on_drop: bool,
}
impl SessionBuilder {
pub fn cpu_count(mut self, cpu_count: u32) -> Self {
self.cpu_count = Some(cpu_count);
self
}
pub fn memory_mb(mut self, memory_mb: u32) -> Self {
self.memory_mb = Some(memory_mb);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout_ms = u32::try_from(timeout.as_millis()).ok();
if self.timeout_ms.is_none() {
self.timeout_ms = Some(u32::MAX);
}
self
}
pub fn enable_gpu(mut self, enable: bool) -> Self {
self.enable_gpu = enable;
self
}
pub fn vhd(mut self, options: VhdOptions) -> Self {
self.vhd = Some(options);
self
}
pub fn terminate_on_drop(mut self, enable: bool) -> Self {
self.terminate_on_drop = enable;
self
}
fn validate(&self) -> Result<()> {
if self.name.trim().is_empty() {
return Err(Error::InvalidInput(
"session name cannot be empty".to_owned(),
));
}
if self.name.contains('\0') {
return Err(Error::Nul("session name".to_owned()));
}
if self.storage_path.as_os_str().is_empty() {
return Err(Error::InvalidInput(
"storage path cannot be empty".to_owned(),
));
}
if self.cpu_count == Some(0) {
return Err(Error::InvalidInput(
"cpu_count must be greater than zero".to_owned(),
));
}
if self.memory_mb == Some(0) {
return Err(Error::InvalidInput(
"memory_mb must be greater than zero".to_owned(),
));
}
if self.timeout_ms == Some(u32::MAX) {
return Err(Error::InvalidInput(
"timeout does not fit in u32 milliseconds".to_owned(),
));
}
Ok(())
}
pub fn start(self) -> Result<Session> {
self.validate()?;
let com = com::try_initialize_mta()?;
let sdk = raw::sdk()?;
let name = strings::wide_str(&self.name);
let storage_path = strings::wide_path(&self.storage_path);
let mut settings = wslc_sys::WslcSessionSettings::default();
raw::map_result(sdk.init_session_settings(
name.as_ptr(),
storage_path.as_ptr(),
&mut settings,
))?;
if let Some(cpu_count) = self.cpu_count {
raw::map_result(sdk.set_session_cpu_count(&mut settings, cpu_count))?;
}
if let Some(memory_mb) = self.memory_mb {
raw::map_result(sdk.set_session_memory(&mut settings, memory_mb))?;
}
if let Some(timeout_ms) = self.timeout_ms {
raw::map_result(sdk.set_session_timeout(&mut settings, timeout_ms))?;
}
if self.enable_gpu {
raw::map_result(sdk.set_session_feature_flags(
&mut settings,
wslc_sys::WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU,
))?;
}
let vhd_name;
let vhd_raw;
if let Some(vhd) = &self.vhd {
vhd_name = strings::cstring(&vhd.name, "vhd name")?;
let (flags, uid, gid) = if let Some((uid, gid)) = vhd.owner {
(wslc_sys::WSLC_VHD_REQ_FLAG_OWNER, uid, gid)
} else {
(wslc_sys::WSLC_VHD_REQ_FLAG_NONE, 0, 0)
};
vhd_raw = wslc_sys::WslcVhdRequirements {
name: vhd_name.as_ptr(),
sizeBytes: vhd.size_bytes,
type_: vhd.vhd_type.as_raw(),
flags,
uid,
gid,
};
raw::map_result(sdk.set_session_vhd(&mut settings, &vhd_raw))?;
}
let raw_session = raw::map_result(sdk.create_session(&mut settings))?;
let raw = NonNull::new(raw_session).ok_or_else(|| {
Error::from_hresult(wslc_sys::S_OK, "WslcCreateSession returned a null session")
})?;
Ok(Session {
inner: Rc::new(SessionInner {
raw,
terminate_on_drop: self.terminate_on_drop,
_com: com,
}),
})
}
}
impl Drop for SessionInner {
fn drop(&mut self) {
if let Ok(sdk) = raw::sdk() {
if self.terminate_on_drop {
let _ = sdk.terminate_session(self.raw.as_ptr());
}
let _ = sdk.release_session(self.raw.as_ptr());
}
}
}