Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use builder pattern #2

Merged
merged 5 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion examples/reflink_block.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fs::File;
use std::num::NonZeroU64;

fn main() -> std::io::Result<()> {
let args: Vec<_> = std::env::args().collect();
Expand All @@ -19,7 +20,16 @@ fn main() -> std::io::Result<()> {
let mut offset = 0u64;
while offset < len as u64 {
println!("reflink {offset}, {cluster_size}");
reflink_copy::reflink_block(&from_file, offset, &to_file, offset, cluster_size)?;
reflink_copy::ReflinkBlockBuilder::default()
.from(&from_file)
.from_offset(offset)
.to(&to_file)
.to_offset(offset)
.src_length(NonZeroU64::new(cluster_size).unwrap())
.cluster_size(NonZeroU64::new(cluster_size).unwrap())
.reflink_block()?;

//reflink_block(&from_file, offset, &to_file, offset, cluster_size)?;
offset += cluster_size;
}
Ok(())
Expand Down
69 changes: 2 additions & 67 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//!
//! As soon as other OSes support the functionality, support will be added.

mod reflink_block;
mod sys;

use std::fs;
Expand Down Expand Up @@ -188,70 +189,4 @@ pub enum ReflinkSupport {
Unknown,
}

/// Creates a reflink of a specified block from one file to another.
///
/// This function is designed to be highly performant and does not perform any extra API calls.
/// It is expected that the user takes care of necessary preliminary checks and preparations.
///
/// If you need to clone an entire file, consider using the [`reflink`] or [`reflink_or_copy`]
/// functions instead.
///
/// > Note: Currently the function works only for windows. It returns `Err` for any other platform.
///
/// # Windows Restrictions and Remarks
/// - The source and destination regions must begin and end at a cluster boundary.
/// - The cloned region must be less than 4GB in length.
/// - The destination region must not extend past the end of file. If the application wishes to
/// extend the destination with cloned data, it must first call
/// [`File::set_len`](fn@std::fs::File::set_len).
/// - If the source and destination regions are in the same file, they must not overlap. (The
/// application may able to proceed by splitting up the block clone operation into multiple block
/// clones that no longer overlap.)
/// - The source and destination files must be on the same ReFS volume.
/// - The source and destination files must have the same Integrity Streams setting (that is,
/// Integrity Streams must be enabled in both files, or disabled in both files).
/// - If the source file is sparse, the destination file must also be sparse.
/// - The block clone operation will break Shared Opportunistic Locks (also known as Level 2
/// Opportunistic Locks).
/// - The ReFS volume must have been formatted with Windows Server 2016, and if Windows Failover
/// Clustering is in use, the Clustering Functional Level must have been Windows Server 2016 or
/// later at format time.
///
/// More information can be found by the
/// [link](https://learn.microsoft.com/en-us/windows/win32/fileio/block-cloning).
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// const CLUSTER_SIZE: u64 = 4096;
/// let from_file = File::open("source.txt")?;
/// let len = from_file.metadata()?.len();
/// let to_file = File::create("destination.txt")?;
/// to_file.set_len(len)?;
/// let mut offset = 0u64;
/// while offset < len {
/// reflink_copy::reflink_block(&from_file, offset, &to_file, offset, CLUSTER_SIZE)?;
/// offset += CLUSTER_SIZE;
/// }
/// if offset > len {
/// to_file.set_len(len)?;
/// }
/// Ok(())
/// }
/// ```
#[cfg_attr(not(windows), allow(unused_variables))]
pub fn reflink_block(
from: &fs::File,
from_offset: u64,
to: &fs::File,
to_offset: u64,
block_size: u64,
) -> io::Result<()> {
#[cfg(windows)]
return sys::reflink_block(from, from_offset, to, to_offset, block_size);
#[cfg(not(windows))]
Err(io::Error::other("Not implemented"))
}
pub use reflink_block::ReflinkBlockBuilder;
146 changes: 146 additions & 0 deletions src/reflink_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::sys;
use std::fs::File;
use std::io;
use std::num::NonZeroU64;

/// Creates a reflink of a specified block from one file to another.
///
/// This functionality is designed to be highly performant and does not perform any extra API calls.
/// It is expected that the user takes care of necessary preliminary checks and preparations.
///
/// If you need to clone an entire file, consider using the [`reflink`] or [`reflink_or_copy`]
/// functions instead.
///
/// > Note: Currently the function works only for windows. It returns `Err` for any other platform.
///
/// # Windows Restrictions and Remarks
/// - The source and destination regions must begin and end at a cluster boundary.
/// - The destination region must not extend past the end of file. If the application wishes to
/// extend the destination with cloned data, it must first call
/// [`File::set_len`](fn@std::fs::File::set_len).
/// - If the source and destination regions are in the same file, they must not overlap. (The
/// application may able to proceed by splitting up the block clone operation into multiple block
/// clones that no longer overlap.)
/// - The source and destination files must be on the same ReFS volume.
/// - The source and destination files must have the same Integrity Streams setting (that is,
/// Integrity Streams must be enabled in both files, or disabled in both files).
/// - If the source file is sparse, the destination file must also be sparse.
/// - The block clone operation will break Shared Opportunistic Locks (also known as Level 2
/// Opportunistic Locks).
/// - The ReFS volume must have been formatted with Windows Server 2016, and if Windows Failover
/// Clustering is in use, the Clustering Functional Level must have been Windows Server 2016 or
/// later at format time.
/// - Note: If block is 4GB or larger, [`ReflinkBlockBuilder::reflink_block`] splits it to multiple
/// smaller blocks with the size of 4GB minus cluster size.
///
/// More information can be found by the
/// [link](https://learn.microsoft.com/en-us/windows/win32/fileio/block-cloning).
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
/// use std::num::NonZeroU64;
///
/// fn main() -> std::io::Result<()> {
/// const CLUSTER_SIZE: u64 = 4096;
/// let from_file = File::open("source.txt")?;
/// let len = from_file.metadata()?.len();
/// let to_file = File::create("destination.txt")?;
/// to_file.set_len(len)?;
/// let mut offset = 0u64;
/// while offset < len {
/// reflink_copy::ReflinkBlockBuilder::new()
/// .from(&from_file)
/// .from_offset(offset)
/// .to(&to_file)
/// .to_offset(offset)
/// .src_length(NonZeroU64::new(CLUSTER_SIZE).unwrap())
/// .cluster_size(NonZeroU64::new(CLUSTER_SIZE).unwrap())
/// .reflink_block()?;
/// offset += CLUSTER_SIZE;
/// }
/// Ok(())
/// }
/// ```
/// [`reflink`]: crate::reflink
/// [`reflink_or_copy`]: crate::reflink_or_copy
#[derive(Debug, Default)]
pub struct ReflinkBlockBuilder<'from, 'to> {
from: Option<&'from File>,
from_offset: u64,
to: Option<&'to File>,
to_offset: u64,
src_length: u64,
cluster_size: Option<NonZeroU64>,
}

impl<'from, 'to> ReflinkBlockBuilder<'from, 'to> {
/// Creates a new instance of [`ReflinkBlockBuilder`].
pub fn new() -> Self {
Self::default()
}

/// Sets the source file.
#[must_use]
pub fn from(mut self, from: &'from File) -> ReflinkBlockBuilder<'from, 'to> {
self.from = Some(from);
self
}

/// Sets the offset within the source file.
#[must_use]
pub fn from_offset(mut self, from_offset: u64) -> Self {
self.from_offset = from_offset;
self
}

/// Sets the destination file.
#[must_use]
pub fn to(mut self, to: &'to File) -> ReflinkBlockBuilder<'from, 'to> {
self.to = Some(to);
self
}

/// Sets the offset within the destination file.
#[must_use]
pub fn to_offset(mut self, to_offset: u64) -> Self {
self.to_offset = to_offset;
self
}

/// Sets the length of the source data to be reflinked.
#[must_use]
pub fn src_length(mut self, src_length: NonZeroU64) -> Self {
self.src_length = src_length.get();
self
}

/// Sets the cluster size. It is used to calculate the max block size of a single reflink call
/// on Windows.
#[must_use]
pub fn cluster_size(mut self, cluster_size: NonZeroU64) -> Self {
self.cluster_size = Some(cluster_size);
self
}

/// Performs reflink operation for the specified block of data.
#[cfg_attr(not(windows), allow(unused_variables))]
pub fn reflink_block(self) -> io::Result<()> {
assert!(self.from.is_some(), "`from` is not set");
assert!(self.to.is_some(), "`to` is not set");
assert_ne!(self.src_length, 0, "`src_length` is not set");

#[cfg(windows)]
return sys::reflink_block(
self.from.unwrap(),
self.from_offset,
self.to.unwrap(),
self.to_offset,
self.src_length,
self.cluster_size,
);
#[cfg(not(windows))]
Err(io::Error::other("Not implemented"))
}
}
2 changes: 1 addition & 1 deletion src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ cfg_if! {
mod windows_impl;
pub use self::windows_impl::reflink;
pub use self::windows_impl::check_reflink_support;
pub use self::windows_impl::reflink_block;
pub(crate) use self::windows_impl::reflink_block;
} else {
pub use self::reflink_not_supported as reflink;
}
Expand Down
79 changes: 54 additions & 25 deletions src/sys/windows_impl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::utility::AutoRemovedFile;
use crate::ReflinkSupport;
use std::num::NonZeroU64;

use std::{
convert::TryInto,
Expand Down Expand Up @@ -90,30 +91,19 @@ pub fn reflink(from: &Path, to: &Path) -> io::Result<()> {
}
};

let mut bytes_copied = 0;
// Must be smaller than 4GB; This is always a multiple of ClusterSize
let max_copy_len: i64 = if cluster_size == 0 {
total_copy_len
let cluster_size = if cluster_size != 0 {
Some(NonZeroU64::new(cluster_size as u64).unwrap())
} else {
(4 * 1024 * 1024 * 1024) - cluster_size
None
};
while bytes_copied < total_copy_len {
let bytes_to_copy = total_copy_len.min(max_copy_len);
if cluster_size != 0 {
debug_assert_eq!(bytes_to_copy % cluster_size, 0);
debug_assert_eq!(bytes_copied % cluster_size, 0);
}

reflink_block(
&src,
bytes_copied as u64,
dest.as_inner_file(),
bytes_copied as u64,
bytes_to_copy as u64,
)?;
bytes_copied += bytes_to_copy;
}

reflink_block(
&src,
0,
dest.as_inner_file(),
0,
total_copy_len as u64,
cluster_size,
)?;
if !src_is_sparse {
dest.unset_sparse()?;
}
Expand Down Expand Up @@ -364,18 +354,57 @@ fn get_volume_flags(volume_path_w: &[u16]) -> io::Result<u32> {
Ok(file_system_flags)
}

pub fn reflink_block(
pub(crate) fn reflink_block(
from: &File,
from_offset: u64,
to: &File,
to_offset: u64,
src_length: u64,
cluster_size: Option<NonZeroU64>,
) -> io::Result<()> {
const GB: u64 = 1024u64 * 1024 * 1024;
const MAX_REFS_CLUSTER_SIZE: u64 = 64 * 1024;

// Must be smaller than 4GB; This is always a multiple of ClusterSize
let max_io_size = 4u64 * GB
- cluster_size
.map(NonZeroU64::get)
.unwrap_or(MAX_REFS_CLUSTER_SIZE);

let mut bytes_copied = 0;
while bytes_copied < src_length {
let bytes_to_copy = max_io_size.min(src_length - bytes_copied);
if let Some(cluster_size) = cluster_size {
debug_assert_eq!(bytes_to_copy % cluster_size, 0);
debug_assert_eq!(bytes_copied % cluster_size, 0);
}

duplicate_extent_to_file(
from,
from_offset + bytes_copied,
to,
to_offset + bytes_copied,
bytes_to_copy,
)?;

bytes_copied += bytes_to_copy;
}

Ok(())
}

fn duplicate_extent_to_file(
from: &File,
from_offset: u64,
to: &File,
to_offset: u64,
block_size: u64,
src_length: u64,
) -> io::Result<()> {
let mut dup_extent = DUPLICATE_EXTENTS_DATA {
FileHandle: from.as_handle(),
SourceFileOffset: from_offset as i64,
TargetFileOffset: to_offset as i64,
ByteCount: block_size as i64,
ByteCount: src_length as i64,
};

let mut bytes_returned = 0u32;
Expand Down
Loading
Loading