Skip to content

Commit 8cb5a38

Browse files
committed
add Windows sparsify_snapshot via FSCTL_SET_SPARSE + FSCTL_SET_ZERO_DATA
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent e0fe29d commit 8cb5a38

4 files changed

Lines changed: 116 additions & 2 deletions

File tree

host/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

host/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ base64 = "0.22"
4040
nix = { version = "0.29", features = ["fs"] }
4141
libc = "0.2"
4242

43+
[target.'cfg(windows)'.dependencies]
44+
windows-sys = { version = "0.61", features = ["Win32_System_IO", "Win32_Storage_FileSystem"] }
45+

host/src/bin/pyhl.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,26 @@ fn disk_mib(p: &Path) -> u64 {
393393
.unwrap_or_else(|_| mib(p))
394394
}
395395

396-
#[cfg(not(unix))]
396+
#[cfg(windows)]
397+
fn disk_mib(p: &Path) -> u64 {
398+
use std::os::windows::ffi::OsStrExt;
399+
let wide: Vec<u16> = p
400+
.as_os_str()
401+
.encode_wide()
402+
.chain(std::iter::once(0))
403+
.collect();
404+
let mut high: u32 = 0;
405+
let low = unsafe {
406+
windows_sys::Win32::Storage::FileSystem::GetCompressedFileSizeW(wide.as_ptr(), &mut high)
407+
};
408+
if low == u32::MAX {
409+
return mib(p);
410+
}
411+
let bytes = ((high as u64) << 32) | (low as u64);
412+
bytes / 1024 / 1024
413+
}
414+
415+
#[cfg(not(any(unix, windows)))]
397416
fn disk_mib(p: &Path) -> u64 {
398417
mib(p)
399418
}

host/src/lib.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1588,7 +1588,98 @@ fn sparsify_snapshot(path: &Path) -> Result<()> {
15881588
Ok(())
15891589
}
15901590

1591-
#[cfg(not(target_os = "linux"))]
1591+
/// Windows equivalent: mark the file sparse with FSCTL_SET_SPARSE, then
1592+
/// punch zero ranges with FSCTL_SET_ZERO_DATA.
1593+
#[cfg(target_os = "windows")]
1594+
fn sparsify_snapshot(path: &Path) -> Result<()> {
1595+
use std::os::windows::io::AsRawHandle;
1596+
use windows_sys::Win32::Storage::FileSystem::{FSCTL_SET_SPARSE, FSCTL_SET_ZERO_DATA};
1597+
use windows_sys::Win32::System::IO::DeviceIoControl;
1598+
1599+
let file = std::fs::OpenOptions::new()
1600+
.read(true)
1601+
.write(true)
1602+
.open(path)?;
1603+
let len = file.metadata()?.len();
1604+
let handle = file.as_raw_handle() as isize;
1605+
1606+
// Mark file as sparse.
1607+
let ok = unsafe {
1608+
DeviceIoControl(
1609+
handle,
1610+
FSCTL_SET_SPARSE,
1611+
std::ptr::null(),
1612+
0,
1613+
std::ptr::null_mut(),
1614+
0,
1615+
std::ptr::null_mut(),
1616+
std::ptr::null_mut(),
1617+
)
1618+
};
1619+
if ok == 0 {
1620+
return Ok(());
1621+
}
1622+
1623+
let mmap = unsafe { memmap2::Mmap::map(&file)? };
1624+
1625+
const PAGE: usize = 4096;
1626+
const HEADER: usize = PAGE;
1627+
let zero_page = [0u8; PAGE];
1628+
1629+
// Coalesce contiguous zero pages into ranges for fewer syscalls.
1630+
let mut punched = 0u64;
1631+
let mut offset = HEADER;
1632+
while offset + PAGE <= len as usize {
1633+
if mmap[offset..offset + PAGE] != zero_page {
1634+
offset += PAGE;
1635+
continue;
1636+
}
1637+
let range_start = offset;
1638+
while offset + PAGE <= len as usize && mmap[offset..offset + PAGE] == zero_page {
1639+
offset += PAGE;
1640+
}
1641+
let range_end = offset;
1642+
1643+
#[repr(C)]
1644+
struct FileZeroDataInformation {
1645+
file_offset: i64,
1646+
beyond_final_zero: i64,
1647+
}
1648+
1649+
let info = FileZeroDataInformation {
1650+
file_offset: range_start as i64,
1651+
beyond_final_zero: range_end as i64,
1652+
};
1653+
let ok = unsafe {
1654+
DeviceIoControl(
1655+
handle,
1656+
FSCTL_SET_ZERO_DATA,
1657+
&info as *const _ as *const _,
1658+
std::mem::size_of::<FileZeroDataInformation>() as u32,
1659+
std::ptr::null_mut(),
1660+
0,
1661+
std::ptr::null_mut(),
1662+
std::ptr::null_mut(),
1663+
)
1664+
};
1665+
if ok != 0 {
1666+
punched += (range_end - range_start) as u64 / PAGE as u64;
1667+
}
1668+
}
1669+
drop(mmap);
1670+
1671+
if punched > 0 {
1672+
eprintln!(
1673+
" sparsified: punched {} zero pages ({} MiB saved on disk)",
1674+
punched,
1675+
punched * 4 / 1024
1676+
);
1677+
}
1678+
1679+
Ok(())
1680+
}
1681+
1682+
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
15921683
fn sparsify_snapshot(_path: &Path) -> Result<()> {
15931684
Ok(())
15941685
}

0 commit comments

Comments
 (0)