#![cfg(target_os = "windows")]
#![allow(unsafe_code)]
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE};
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ,
FILE_SHARE_WRITE, OPEN_EXISTING,
};
use crate::windows::wclayer::{self, LayerChain};
const SANDBOX_VHDX: &str = "sandbox.vhdx";
#[derive(Debug)]
pub struct WritableLayer {
layer_path: PathBuf,
vhd_mount_path: String,
filter_attached: bool,
needs_destroy: bool,
}
impl WritableLayer {
#[must_use]
pub fn layer_path(&self) -> &Path {
&self.layer_path
}
#[must_use]
pub fn vhd_mount_path(&self) -> &str {
&self.vhd_mount_path
}
pub fn detach_and_destroy(mut self) -> io::Result<()> {
let detach_res = if self.filter_attached {
wclayer::detach_layer_storage_filter(&self.layer_path)
} else {
Ok(())
};
self.filter_attached = false;
let destroy_res = if self.needs_destroy {
wclayer::destroy_layer(&self.layer_path)
} else {
Ok(())
};
self.needs_destroy = false;
detach_res.and(destroy_res)
}
}
impl Drop for WritableLayer {
fn drop(&mut self) {
if self.filter_attached {
if let Err(e) = wclayer::detach_layer_storage_filter(&self.layer_path) {
tracing::warn!(
layer = %self.layer_path.display(),
error = %e,
"detach_layer_storage_filter failed on drop",
);
}
}
if self.needs_destroy {
if let Err(e) = wclayer::destroy_layer(&self.layer_path) {
tracing::warn!(
layer = %self.layer_path.display(),
error = %e,
"destroy_layer failed on drop",
);
}
}
}
}
pub fn create(
layer_path: &Path,
parent_chain: &LayerChain,
size_gb: u64,
is_base_os_bootstrap: bool,
) -> io::Result<WritableLayer> {
std::fs::create_dir_all(layer_path)?;
let options_json = build_init_options_json(size_gb);
if let Err(e) = wclayer::initialize_writable_layer(layer_path, parent_chain, &options_json) {
cleanup_best_effort(layer_path, false);
return Err(e);
}
let vhd_path = layer_path.join(SANDBOX_VHDX);
let vhd_handle = match open_sandbox_vhd(&vhd_path) {
Ok(h) => h,
Err(e) => {
cleanup_best_effort(layer_path, false);
return Err(e);
}
};
let format_result = wclayer::format_writable_layer_vhd(vhd_handle);
let base_os_result = if format_result.is_ok() && is_base_os_bootstrap {
wclayer::setup_base_os_layer(layer_path, vhd_handle, "{}")
} else {
Ok(())
};
let mount_result = if format_result.is_ok() && base_os_result.is_ok() {
wclayer::get_layer_vhd_mount_path(vhd_handle)
} else {
Ok(String::new())
};
unsafe {
if let Err(e) = CloseHandle(vhd_handle) {
tracing::warn!(error = %e, "CloseHandle(sandbox.vhdx) failed");
}
}
if let Err(e) = format_result {
cleanup_best_effort(layer_path, false);
return Err(e);
}
if let Err(e) = base_os_result {
cleanup_best_effort(layer_path, false);
return Err(e);
}
let vhd_mount_path = match mount_result {
Ok(p) => p,
Err(e) => {
cleanup_best_effort(layer_path, false);
return Err(e);
}
};
if let Err(e) = wclayer::attach_layer_storage_filter(layer_path, parent_chain) {
cleanup_best_effort(layer_path, false);
return Err(e);
}
Ok(WritableLayer {
layer_path: layer_path.to_path_buf(),
vhd_mount_path,
filter_attached: true,
needs_destroy: true,
})
}
fn build_init_options_json(size_gb: u64) -> String {
if size_gb == 0 {
return String::from("{}");
}
let bytes = size_gb.saturating_mul(1024 * 1024 * 1024);
format!(r#"{{"SandboxSize":{bytes}}}"#)
}
fn open_sandbox_vhd(path: &Path) -> io::Result<HANDLE> {
let wide: Vec<u16> = path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
CreateFileW(
PCWSTR::from_raw(wide.as_ptr()),
GENERIC_READ.0 | GENERIC_WRITE.0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
None,
)
}
.map_err(|e| io::Error::other(format!("CreateFileW({}): {e}", path.display())))
}
fn cleanup_best_effort(layer_path: &Path, filter_attached: bool) {
if filter_attached {
if let Err(e) = wclayer::detach_layer_storage_filter(layer_path) {
tracing::warn!(
layer = %layer_path.display(),
error = %e,
"detach_layer_storage_filter failed during rollback",
);
}
}
if let Err(e) = wclayer::destroy_layer(layer_path) {
tracing::warn!(
layer = %layer_path.display(),
error = %e,
"destroy_layer failed during rollback",
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_options_zero_size_is_defaults() {
assert_eq!(build_init_options_json(0), "{}");
}
#[test]
fn init_options_size_encodes_bytes() {
let json = build_init_options_json(20);
assert!(json.contains("\"SandboxSize\""));
assert!(json.contains(&(20u64 * 1024 * 1024 * 1024).to_string()));
}
#[test]
fn init_options_saturates_on_huge_size() {
let json = build_init_options_json(u64::MAX);
assert!(json.starts_with("{\"SandboxSize\":"));
}
}