#![cfg(target_os = "windows")]
#![allow(unsafe_code)]
use std::io;
use std::path::Path;
use serde::Serialize;
use windows::core::{HSTRING, PWSTR};
use windows::Win32::Foundation::{LocalFree, HANDLE, HLOCAL};
use windows::Win32::System::HostComputeSystem::{
HcsAttachLayerStorageFilter, HcsDestroyLayer, HcsDetachLayerStorageFilter, HcsExportLayer,
HcsFormatWritableLayerVhd, HcsGetLayerVhdMountPath, HcsImportLayer, HcsInitializeWritableLayer,
HcsSetupBaseOSLayer,
};
use zlayer_hcs::schema::{Layer, SchemaVersion};
#[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 attach_layer_storage_filter(layer_path: &Path, parent_chain: &LayerChain) -> io::Result<()> {
let layer_data = parent_chain.to_layer_data_json()?;
let lp = path_to_hstring(layer_path);
let ld = HSTRING::from(layer_data);
unsafe {
HcsAttachLayerStorageFilter(&lp, &ld)
.map_err(|e| io::Error::other(format!("HcsAttachLayerStorageFilter: {e}")))?;
}
Ok(())
}
pub fn detach_layer_storage_filter(layer_path: &Path) -> io::Result<()> {
let lp = path_to_hstring(layer_path);
unsafe {
HcsDetachLayerStorageFilter(&lp)
.map_err(|e| io::Error::other(format!("HcsDetachLayerStorageFilter: {e}")))?;
}
Ok(())
}
pub fn initialize_writable_layer(
writable_layer_path: &Path,
parent_chain: &LayerChain,
options_json: &str,
) -> io::Result<()> {
let layer_data = parent_chain.to_layer_data_json()?;
let wp = path_to_hstring(writable_layer_path);
let ld = HSTRING::from(layer_data);
let opts = HSTRING::from(options_json);
unsafe {
HcsInitializeWritableLayer(&wp, &ld, &opts)
.map_err(|e| io::Error::other(format!("HcsInitializeWritableLayer: {e}")))?;
}
Ok(())
}
pub fn format_writable_layer_vhd(vhd_handle: HANDLE) -> io::Result<()> {
unsafe {
HcsFormatWritableLayerVhd(vhd_handle)
.map_err(|e| io::Error::other(format!("HcsFormatWritableLayerVhd: {e}")))?;
}
Ok(())
}
pub fn setup_base_os_layer(
layer_path: &Path,
vhd_handle: HANDLE,
options_json: &str,
) -> io::Result<()> {
let lp = path_to_hstring(layer_path);
let opts = HSTRING::from(options_json);
unsafe {
HcsSetupBaseOSLayer(&lp, vhd_handle, &opts)
.map_err(|e| io::Error::other(format!("HcsSetupBaseOSLayer: {e}")))?;
}
Ok(())
}
pub fn get_layer_vhd_mount_path(vhd_handle: HANDLE) -> io::Result<String> {
let pwstr: PWSTR = unsafe {
HcsGetLayerVhdMountPath(vhd_handle)
.map_err(|e| io::Error::other(format!("HcsGetLayerVhdMountPath: {e}")))?
};
if pwstr.is_null() {
return Ok(String::new());
}
let decoded = unsafe { pwstr.to_string() };
unsafe {
let _ = LocalFree(Some(HLOCAL(pwstr.0.cast())));
}
decoded.map_err(|e| io::Error::other(format!("UTF-16 mount path decode: {e}")))
}
#[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\""));
}
}