#![allow(unsafe_code)]
use std::ffi::c_void;
use std::io::{self, BufReader, BufWriter};
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CopyFileExW, CreateFileW, MoveFileExW, SetEndOfFile, SetFilePointerEx,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use crate::copy::ReflinkMode;
const GENERIC_READ: u32 = 0x8000_0000;
const GENERIC_WRITE: u32 = 0x4000_0000;
const FILE_SHARE_READ: u32 = 0x0000_0001;
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
const OPEN_EXISTING: u32 = 3;
const CREATE_ALWAYS: u32 = 2;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x0000_0080;
const FILE_BEGIN: u32 = 0;
const MOVEFILE_REPLACE_EXISTING: u32 = 0x0000_0001;
const MOVEFILE_WRITE_THROUGH: u32 = 0x0000_0008;
const FSCTL_DUPLICATE_EXTENTS_TO_FILE: u32 = 0x0009_8344;
const CLUSTER_ALIGN: i64 = 64 * 1024;
const MAX_CLONE_CHUNK: i64 = 256 * 1024 * 1024;
#[repr(C)]
struct DuplicateExtentsData {
file_handle: HANDLE,
source_file_offset: i64,
target_file_offset: i64,
byte_count: i64,
}
fn wide(path: &Path) -> Vec<u16> {
path.as_os_str().encode_wide().chain(Some(0)).collect()
}
fn last_error() -> io::Error {
let code = unsafe { GetLastError() };
io::Error::from_raw_os_error(i32::try_from(code).unwrap_or(-1))
}
struct OwnedHandle(HANDLE);
impl Drop for OwnedHandle {
fn drop(&mut self) {
if self.0 != INVALID_HANDLE_VALUE && !self.0.is_null() {
unsafe {
CloseHandle(self.0);
}
}
}
}
fn create_handle(path_w: &[u16], access: u32, disposition: u32) -> io::Result<OwnedHandle> {
let handle = unsafe {
CreateFileW(
path_w.as_ptr(),
access,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null(),
disposition,
FILE_ATTRIBUTE_NORMAL,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(last_error());
}
Ok(OwnedHandle(handle))
}
fn set_file_size(handle: HANDLE, size: i64) -> io::Result<()> {
let mut new_pos = 0_i64;
let moved = unsafe { SetFilePointerEx(handle, size, &raw mut new_pos, FILE_BEGIN) };
if moved == 0 {
return Err(last_error());
}
let ended = unsafe { SetEndOfFile(handle) };
if ended == 0 {
return Err(last_error());
}
Ok(())
}
#[inline]
fn round_up(value: i64, align: i64) -> i64 {
(value + align - 1) / align * align
}
fn block_clone(src_w: &[u16], dst_w: &[u16], len: u64) -> io::Result<u64> {
let src = create_handle(src_w, GENERIC_READ, OPEN_EXISTING)?;
let dst = create_handle(dst_w, GENERIC_READ | GENERIC_WRITE, CREATE_ALWAYS)?;
let len_i = i64::try_from(len).map_err(|_| io::Error::other("file too large to clone"))?;
let aligned = round_up(len_i, CLUSTER_ALIGN);
set_file_size(dst.0, aligned)?;
let mut offset = 0_i64;
while offset < aligned {
let chunk = (aligned - offset).min(MAX_CLONE_CHUNK);
let data = DuplicateExtentsData {
file_handle: src.0,
source_file_offset: offset,
target_file_offset: offset,
byte_count: chunk,
};
let mut returned = 0_u32;
let ok = unsafe {
DeviceIoControl(
dst.0,
FSCTL_DUPLICATE_EXTENTS_TO_FILE,
ptr::from_ref(&data).cast::<c_void>(),
u32::try_from(size_of::<DuplicateExtentsData>()).unwrap_or(0),
ptr::null_mut(),
0,
&raw mut returned,
ptr::null_mut(),
)
};
if ok == 0 {
return Err(last_error());
}
offset += chunk;
}
set_file_size(dst.0, len_i)?;
Ok(len)
}
fn copy_file_ex(src_w: &[u16], dst_w: &[u16]) -> io::Result<()> {
let ok = unsafe {
CopyFileExW(
src_w.as_ptr(),
dst_w.as_ptr(),
None,
ptr::null(),
ptr::null_mut(),
0,
)
};
if ok == 0 {
return Err(last_error());
}
Ok(())
}
fn buffered_copy(src: &Path, dst: &Path) -> io::Result<u64> {
const BUF: usize = 1 << 20;
let reader = std::fs::File::open(src)?;
let writer = std::fs::File::create(dst)?;
let mut r = BufReader::with_capacity(BUF, reader);
let mut w = BufWriter::with_capacity(BUF, writer);
let n = io::copy(&mut r, &mut w)?;
w.into_inner()?;
Ok(n)
}
pub fn copy_file_into(src: &Path, dst: &Path, reflink: ReflinkMode) -> io::Result<u64> {
let src_w = wide(src);
let dst_w = wide(dst);
let len = std::fs::metadata(src)?.len();
if reflink != ReflinkMode::Never {
match block_clone(&src_w, &dst_w, len) {
Ok(n) => return Ok(n),
Err(e) => {
let _ = std::fs::remove_file(dst);
if reflink == ReflinkMode::Always {
return Err(e);
}
}
}
}
if copy_file_ex(&src_w, &dst_w).is_ok() {
Ok(len)
} else {
let _ = std::fs::remove_file(dst);
buffered_copy(src, dst)
}
}
pub fn replace_file(tmp: &Path, target: &Path) -> io::Result<()> {
let tmp_w = wide(tmp);
let target_w = wide(target);
let ok = unsafe {
MoveFileExW(
tmp_w.as_ptr(),
target_w.as_ptr(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
)
};
if ok == 0 {
return Err(last_error());
}
Ok(())
}