Skip to content

Commit

Permalink
Implement reflink_block for lunix (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vaiz authored Jan 18, 2025
1 parent ea03e31 commit 497845a
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 13 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ version = "0.38.20"
default-features = false
features = ["fs", "std"]

[target.'cfg(target_os = "linux")'.dependencies.libc]
version = "0.2.169"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.59.0", features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_SystemServices"] }

Expand Down
16 changes: 12 additions & 4 deletions examples/reflink_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@ fn main() -> std::io::Result<()> {
to_file.set_len(len)?;

let mut offset = 0u64;
while offset < len as u64 {
println!("reflink {offset}, {cluster_size}");
reflink_copy::ReflinkBlockBuilder::new(&from_file, &to_file, cluster_size)
while offset < len {
let src_length = if cfg!(windows) {
// Windows API clones the entire cluster regardless of the number of bytes actually used
// by the file in that cluster.
cluster_size
} else {
cluster_size.min(NonZeroU64::new(len - offset).unwrap())
};

println!("reflink {offset}, {src_length}");
reflink_copy::ReflinkBlockBuilder::new(&from_file, &to_file, src_length)
.from_offset(offset)
.to_offset(offset)
.cluster_size(cluster_size)
.reflink_block()?;

offset += cluster_size.get();
offset += src_length.get();
}
Ok(())
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ pub fn reflink_or_copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Resu
/// Ok(())
/// }
/// ```
#[cfg_attr(not(windows), allow(unused_variables))]
pub fn check_reflink_support(
from: impl AsRef<Path>,
to: impl AsRef<Path>,
Expand Down
26 changes: 19 additions & 7 deletions src/reflink_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,28 @@ use std::num::NonZeroU64;
/// 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.
/// > Note: Currently the function works only for windows and linux platforms. It returns `Err` for
/// any other platform.
///
/// # General restrictions
///
/// - The source and destination regions must begin and end at a cluster boundary.
/// - 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.)
/// - `src_length` equal to 0 is not supported.
///
/// # Linux specific restrictions and remarks
///
/// - If the file size is not aligned to the cluster size, the reflink operation must not exceed
/// the file length. For example, to reflink the whole file with size of 7000 bytes, `src_length`
/// should be 7000 bytes.
///
/// More information about block cloning on Linux can be found by the
/// [link](https://www.man7.org/linux/man-pages/man2/ioctl_ficlonerange.2.html).
///
/// # Windows specific restrictions and remarks
///
/// - 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).
Expand All @@ -32,6 +45,9 @@ use std::num::NonZeroU64;
/// - 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.
/// - If the file size is not aligned to the cluster size, the reflink operation should still
/// be aligned by the cluster size. For example, to reflink the whole file with size of 7000 bytes
/// and a cluster size of 4096 bytes, `src_length` should be 8192 bytes.
///
/// > Note: In order to handle blocks larger than 4GB,
/// [`ReflinkBlockBuilder::reflink_block`] splits these big blocks into smaller ones.
Expand Down Expand Up @@ -117,18 +133,14 @@ impl<'from, 'to> ReflinkBlockBuilder<'from, 'to> {
}

/// Performs reflink operation for the specified block of data.
#[cfg_attr(not(windows), allow(unused_variables))]
pub fn reflink_block(self) -> io::Result<()> {
#[cfg(windows)]
return sys::reflink_block(
sys::reflink_block(
self.from,
self.from_offset,
self.to,
self.to_offset,
self.src_length,
self.cluster_size,
);
#[cfg(not(windows))]
Err(io::Error::other("Not implemented"))
)
}
}
15 changes: 15 additions & 0 deletions src/sys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::path::Path;
use std::{fs, io};

use cfg_if::cfg_if;

Expand All @@ -8,17 +9,31 @@ cfg_if! {
if #[cfg(unix)] {
mod unix;
pub use self::unix::reflink;
pub(crate) use self::unix::reflink_block;
} else if #[cfg(windows)] {
mod windows_impl;
pub use self::windows_impl::reflink;
pub use self::windows_impl::check_reflink_support;
pub(crate) use self::windows_impl::reflink_block;
} else {
pub use self::reflink_not_supported as reflink;
pub(crate) use self::reflink_block_not_supported as reflink_block;
}
}

#[allow(dead_code)]
pub fn reflink_not_supported(_from: &Path, _to: &Path) -> std::io::Result<()> {
Err(std::io::ErrorKind::Unsupported.into())
}

#[allow(dead_code)]
pub(crate) fn reflink_block_not_supported(
_from: &fs::File,
_from_offset: u64,
_to: &fs::File,
_to_offset: u64,
_src_length: u64,
_cluster_size: Option<std::num::NonZeroU64>,
) -> io::Result<()> {
Err(std::io::ErrorKind::Unsupported.into())
}
34 changes: 34 additions & 0 deletions src/sys/unix/linux.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::convert::TryInto;
use std::os::unix::io::AsRawFd;
use std::{fs, io, path::Path};

use crate::sys::utility::AutoRemovedFile;
Expand All @@ -12,3 +14,35 @@ pub fn reflink(from: &Path, to: &Path) -> io::Result<()> {
dest.persist();
Ok(())
}

#[cfg(target_os = "linux")]
pub(crate) fn reflink_block(
from: &fs::File,
from_offset: u64,
to: &fs::File,
to_offset: u64,
src_length: u64,
_cluster_size: Option<std::num::NonZeroU64>,
) -> io::Result<()> {
let ret = unsafe {
libc::ioctl(
to.as_raw_fd(),
libc::FICLONERANGE.try_into().unwrap(),
&libc::file_clone_range {
src_fd: from.as_raw_fd().into(),
src_offset: from_offset,
src_length,
dest_offset: to_offset,
},
)
};

if ret == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}

#[cfg(target_os = "android")]
pub(crate) use crate::sys::reflink_block_not_supported as reflink_block;
3 changes: 3 additions & 0 deletions src/sys/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
mod linux;
pub use linux::reflink;
pub(crate) use linux::reflink_block;
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] {
mod macos;
pub use macos::reflink;
pub(crate) use super::reflink_block_not_supported as reflink_block;
} else {
pub use super::reflink_not_supported as reflink;
pub(crate) use super::reflink_block_not_supported as reflink_block;
}
}

0 comments on commit 497845a

Please sign in to comment.