#![cfg(target_os = "windows")]
#![allow(unsafe_code)]
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use serde::Serialize;
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::System::HostComputeSystem::{HcsDestroyLayer, HcsExportLayer, HcsImportLayer};
use zlayer_hcs::schema::{Layer, SchemaVersion};
#[repr(C)]
struct WcDriverInfo {
flavour: u32,
info_buffer: *const u16,
}
#[repr(C)]
struct WcLayerDescriptor {
layer_id: windows::core::GUID,
flags: u32,
_flags_pad: u32,
path: *const u16,
}
static EMPTY_HOME_DIR: [u16; 1] = [0];
fn driver_info() -> WcDriverInfo {
WcDriverInfo {
flavour: 1,
info_buffer: EMPTY_HOME_DIR.as_ptr(),
}
}
fn parent_chain_to_descriptors(
parent_chain: &LayerChain,
) -> io::Result<(Vec<Vec<u16>>, Vec<WcLayerDescriptor>)> {
let path_buffers: Vec<Vec<u16>> = parent_chain
.0
.iter()
.map(|l| {
let mut w: Vec<u16> = Path::new(&l.path).as_os_str().encode_wide().collect();
w.push(0);
w
})
.collect();
let descriptors: Vec<WcLayerDescriptor> = parent_chain
.0
.iter()
.zip(path_buffers.iter())
.map(|(l, buf)| {
let id = parse_guid_str(&l.id)?;
Ok(WcLayerDescriptor {
layer_id: id,
flags: 0,
_flags_pad: 0,
path: buf.as_ptr(),
})
})
.collect::<io::Result<Vec<_>>>()?;
Ok((path_buffers, descriptors))
}
#[derive(Debug, Default, Clone)]
pub struct LayerChain(pub Vec<Layer>);
impl LayerChain {
#[must_use]
pub fn new(layers: Vec<Layer>) -> Self {
Self(layers)
}
fn to_layer_data_json(&self) -> io::Result<String> {
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct LayerData<'a> {
schema_version: SchemaVersion,
#[serde(skip_serializing_if = "<[Layer]>::is_empty")]
layers: &'a [Layer],
}
let data = LayerData {
schema_version: SchemaVersion::default(),
layers: &self.0,
};
serde_json::to_string(&data)
.map_err(|e| io::Error::other(format!("serialize LayerData: {e}")))
}
}
fn path_to_hstring(path: &Path) -> HSTRING {
HSTRING::from(path.as_os_str())
}
pub fn import_layer(
layer_path: &Path,
source_folder: &Path,
parent_chain: &LayerChain,
) -> io::Result<()> {
let layer_data = parent_chain.to_layer_data_json()?;
let lp = path_to_hstring(layer_path);
let sf = path_to_hstring(source_folder);
let ld = HSTRING::from(layer_data);
unsafe {
HcsImportLayer(&lp, &sf, &ld)
.map_err(|e| io::Error::other(format!("HcsImportLayer: {e}")))?;
}
Ok(())
}
pub fn export_layer(
layer_path: &Path,
export_folder: &Path,
parent_chain: &LayerChain,
options_json: &str,
) -> io::Result<()> {
let layer_data = parent_chain.to_layer_data_json()?;
let lp = path_to_hstring(layer_path);
let ef = path_to_hstring(export_folder);
let ld = HSTRING::from(layer_data);
let opts = HSTRING::from(options_json);
unsafe {
HcsExportLayer(&lp, &ef, &ld, &opts)
.map_err(|e| io::Error::other(format!("HcsExportLayer: {e}")))?;
}
Ok(())
}
pub fn destroy_layer(layer_path: &Path) -> io::Result<()> {
let lp = path_to_hstring(layer_path);
unsafe {
HcsDestroyLayer(&lp).map_err(|e| io::Error::other(format!("HcsDestroyLayer: {e}")))?;
}
Ok(())
}
pub fn activate_layer(layer_path: &Path) -> io::Result<()> {
windows::core::link!(
"vmcompute.dll" "system" fn ActivateLayer(
info: *const WcDriverInfo,
id: PCWSTR,
) -> windows::core::HRESULT
);
let info = driver_info();
let lp = HSTRING::from(layer_path.as_os_str());
let hr = unsafe { ActivateLayer(&info, PCWSTR::from_raw(lp.as_ptr())) };
hr.ok()
.map_err(|e| io::Error::other(format!("ActivateLayer: {e}")))?;
Ok(())
}
pub fn deactivate_layer(layer_path: &Path) -> io::Result<()> {
windows::core::link!(
"vmcompute.dll" "system" fn DeactivateLayer(
info: *const WcDriverInfo,
id: PCWSTR,
) -> windows::core::HRESULT
);
let info = driver_info();
let lp = HSTRING::from(layer_path.as_os_str());
let hr = unsafe { DeactivateLayer(&info, PCWSTR::from_raw(lp.as_ptr())) };
hr.ok()
.map_err(|e| io::Error::other(format!("DeactivateLayer: {e}")))?;
Ok(())
}
pub fn prepare_layer(layer_path: &Path, parent_chain: &LayerChain) -> io::Result<()> {
windows::core::link!(
"vmcompute.dll" "system" fn PrepareLayer(
info: *const WcDriverInfo,
id: PCWSTR,
descriptors: *const WcLayerDescriptor,
descriptor_count: usize,
) -> windows::core::HRESULT
);
let info = driver_info();
let lp = HSTRING::from(layer_path.as_os_str());
let (_path_buffers, descriptors) = parent_chain_to_descriptors(parent_chain)?;
let hr = unsafe {
PrepareLayer(
&info,
PCWSTR::from_raw(lp.as_ptr()),
descriptors.as_ptr(),
descriptors.len(),
)
};
hr.ok()
.map_err(|e| io::Error::other(format!("PrepareLayer: {e}")))?;
Ok(())
}
pub fn unprepare_layer(layer_path: &Path) -> io::Result<()> {
windows::core::link!(
"vmcompute.dll" "system" fn UnprepareLayer(
info: *const WcDriverInfo,
id: PCWSTR,
) -> windows::core::HRESULT
);
let info = driver_info();
let lp = HSTRING::from(layer_path.as_os_str());
let hr = unsafe { UnprepareLayer(&info, PCWSTR::from_raw(lp.as_ptr())) };
hr.ok()
.map_err(|e| io::Error::other(format!("UnprepareLayer: {e}")))?;
Ok(())
}
pub fn process_base_layer(layer_path: &Path) -> io::Result<()> {
let lp = path_to_hstring(layer_path);
windows::core::link!(
"vmcompute.dll" "system" fn ProcessBaseImage(path: PCWSTR) -> windows::core::HRESULT
);
tracing::debug!(
target: "zlayer_agent::wclayer",
path = %layer_path.display(),
"calling vmcompute.dll!ProcessBaseImage",
);
let hr = unsafe { ProcessBaseImage(PCWSTR::from_raw(lp.as_ptr())) };
tracing::debug!(
target: "zlayer_agent::wclayer",
path = %layer_path.display(),
hr = ?hr,
"vmcompute.dll!ProcessBaseImage returned",
);
hr.ok()
.map_err(|e| io::Error::other(format!("ProcessBaseImage: {e}")))?;
Ok(())
}
pub fn process_utility_vm_image(utility_vm_path: &Path) -> io::Result<()> {
let lp = path_to_hstring(utility_vm_path);
windows::core::link!(
"vmcompute.dll" "system" fn ProcessUtilityImage(path: PCWSTR) -> windows::core::HRESULT
);
tracing::debug!(
target: "zlayer_agent::wclayer",
path = %utility_vm_path.display(),
"calling vmcompute.dll!ProcessUtilityImage",
);
let hr = unsafe { ProcessUtilityImage(PCWSTR::from_raw(lp.as_ptr())) };
tracing::debug!(
target: "zlayer_agent::wclayer",
path = %utility_vm_path.display(),
hr = ?hr,
"vmcompute.dll!ProcessUtilityImage returned",
);
hr.ok()
.map_err(|e| io::Error::other(format!("ProcessUtilityImage: {e}")))?;
Ok(())
}
pub fn create_sandbox_layer(layer_path: &Path, parent_chain: &LayerChain) -> io::Result<()> {
windows::core::link!(
"vmcompute.dll" "system" fn CreateSandboxLayer(
info: *const WcDriverInfo,
id: PCWSTR,
parent: usize,
descriptors: *const WcLayerDescriptor,
descriptor_count: usize,
) -> windows::core::HRESULT
);
let info = driver_info();
let layer_path_w = HSTRING::from(layer_path.as_os_str());
let (_path_buffers, descriptors) = parent_chain_to_descriptors(parent_chain)?;
let hr = unsafe {
CreateSandboxLayer(
&info,
PCWSTR::from_raw(layer_path_w.as_ptr()),
0,
descriptors.as_ptr(),
descriptors.len(),
)
};
hr.ok()
.map_err(|e| io::Error::other(format!("CreateSandboxLayer: {e}")))?;
Ok(())
}
pub fn link_relative(dest_root: &Path, target_abs: &Path, link_abs: &Path) -> io::Result<()> {
if !target_abs.starts_with(dest_root) {
return Err(io::Error::other(format!(
"link target {} is outside layer root {}",
target_abs.display(),
dest_root.display(),
)));
}
if !link_abs.starts_with(dest_root) {
return Err(io::Error::other(format!(
"link path {} is outside layer root {}",
link_abs.display(),
dest_root.display(),
)));
}
if let Some(parent) = link_abs.parent() {
if std::fs::create_dir_all(parent).is_err() {
let mut to_create: Vec<&Path> = parent.ancestors().collect();
to_create.reverse();
for component in to_create {
if component.as_os_str().is_empty() || component.is_dir() {
continue;
}
crate::windows::layer::create_long_path_dir(component)?;
}
}
}
std::fs::hard_link(target_abs, link_abs)
}
fn parse_guid_str(s: &str) -> io::Result<windows::core::GUID> {
let bytes = s.as_bytes();
if bytes.len() != 36
|| bytes[8] != b'-'
|| bytes[13] != b'-'
|| bytes[18] != b'-'
|| bytes[23] != b'-'
{
return Err(io::Error::other(format!(
"parse_guid_str: malformed GUID {s:?}"
)));
}
let d1 = u32::from_str_radix(&s[0..8], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d1: {e}")))?;
let d2 = u16::from_str_radix(&s[9..13], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d2: {e}")))?;
let d3 = u16::from_str_radix(&s[14..18], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d3: {e}")))?;
let mut d4 = [0u8; 8];
d4[0] = u8::from_str_radix(&s[19..21], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d4[0]: {e}")))?;
d4[1] = u8::from_str_radix(&s[21..23], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d4[1]: {e}")))?;
for i in 0..6 {
let start = 24 + i * 2;
d4[2 + i] = u8::from_str_radix(&s[start..start + 2], 16)
.map_err(|e| io::Error::other(format!("parse_guid_str d4[{}]: {e}", 2 + i)))?;
}
Ok(windows::core::GUID {
data1: d1,
data2: d2,
data3: d3,
data4: d4,
})
}
pub fn layer_id_for_path(layer_path: &Path) -> io::Result<String> {
let basename = layer_path.file_name().ok_or_else(|| {
io::Error::other(format!(
"layer_id_for_path: no basename in {}",
layer_path.display()
))
})?;
let name_w = HSTRING::from(basename);
windows::core::link!(
"vmcompute.dll" "system" fn NameToGuid(name: PCWSTR, guid: *mut windows::core::GUID) -> windows::core::HRESULT
);
let mut guid = windows::core::GUID::zeroed();
let hr = unsafe { NameToGuid(PCWSTR::from_raw(name_w.as_ptr()), &mut guid) };
hr.ok()
.map_err(|e| io::Error::other(format!("NameToGuid({basename:?}): {e}")))?;
Ok(format!(
"{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
guid.data1,
guid.data2,
guid.data3,
guid.data4[0],
guid.data4[1],
guid.data4[2],
guid.data4[3],
guid.data4[4],
guid.data4[5],
guid.data4[6],
guid.data4[7],
))
}
pub fn get_layer_mount_path(layer_path: &Path) -> io::Result<String> {
windows::core::link!(
"vmcompute.dll" "system" fn GetLayerMountPath(
info: *const WcDriverInfo,
id: PCWSTR,
length: *mut usize,
buffer: *mut u16,
) -> windows::core::HRESULT
);
let info = driver_info();
let lp = HSTRING::from(layer_path.as_os_str());
let mut length: usize = 0;
let hr = unsafe {
GetLayerMountPath(
&info,
PCWSTR::from_raw(lp.as_ptr()),
&mut length,
std::ptr::null_mut(),
)
};
hr.ok()
.map_err(|e| io::Error::other(format!("GetLayerMountPath(size query): {e}")))?;
if length == 0 {
return Ok(String::new());
}
let mut buf = vec![0u16; length];
let hr = unsafe {
GetLayerMountPath(
&info,
PCWSTR::from_raw(lp.as_ptr()),
&mut length,
buf.as_mut_ptr(),
)
};
hr.ok()
.map_err(|e| io::Error::other(format!("GetLayerMountPath(fill): {e}")))?;
let end = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16(&buf[..end])
.map_err(|e| io::Error::other(format!("UTF-16 mount path decode: {e}")))
}
pub fn grant_vm_access(vm_id: windows::core::GUID, file_path: &Path) -> io::Result<()> {
let vm_id_str = format!(
"{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
vm_id.data1,
vm_id.data2,
vm_id.data3,
vm_id.data4[0],
vm_id.data4[1],
vm_id.data4[2],
vm_id.data4[3],
vm_id.data4[4],
vm_id.data4[5],
vm_id.data4[6],
vm_id.data4[7],
);
let vm_id_h = windows::core::HSTRING::from(vm_id_str.as_str());
let path_h = path_to_hstring(file_path);
windows::core::link!(
"vmcompute.dll" "system" fn GrantVmAccess(
vm_id: windows::core::PCWSTR,
file_path: windows::core::PCWSTR,
) -> windows::core::HRESULT
);
tracing::debug!(
target: "zlayer_agent::wclayer",
vm_id = %vm_id_str,
path = %file_path.display(),
"calling vmcompute.dll!GrantVmAccess",
);
let hr = unsafe {
GrantVmAccess(
windows::core::PCWSTR::from_raw(vm_id_h.as_ptr()),
windows::core::PCWSTR::from_raw(path_h.as_ptr()),
)
};
tracing::debug!(
target: "zlayer_agent::wclayer",
vm_id = %vm_id_str,
path = %file_path.display(),
hr = ?hr,
"vmcompute.dll!GrantVmAccess returned",
);
hr.ok().map_err(|e| {
io::Error::other(format!(
"GrantVmAccess({vm_id_str}, {}): {e}",
file_path.display()
))
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_chain_serializes_without_layers_key() {
let chain = LayerChain::default();
let json = chain.to_layer_data_json().expect("serialize");
assert!(json.contains("\"SchemaVersion\""));
assert!(!json.contains("\"Layers\""));
}
#[test]
fn non_empty_chain_serializes_parents_in_order() {
let chain = LayerChain::new(vec![
Layer {
id: "1111".into(),
path: r"C:\layers\a".into(),
},
Layer {
id: "2222".into(),
path: r"C:\layers\b".into(),
},
]);
let json = chain.to_layer_data_json().expect("serialize");
assert!(json.contains("\"Layers\""));
let a = json.find("1111").expect("first id present");
let b = json.find("2222").expect("second id present");
assert!(a < b, "child layer must appear before parent");
assert!(json.contains("\"Id\""));
assert!(json.contains("\"Path\""));
}
}