Skip to content

Commit 497845a

Browse files
authored
Implement reflink_block for lunix (#93)
1 parent ea03e31 commit 497845a

File tree

8 files changed

+90
-13
lines changed

8 files changed

+90
-13
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ version = "0.38.20"
2222
default-features = false
2323
features = ["fs", "std"]
2424

25+
[target.'cfg(target_os = "linux")'.dependencies.libc]
26+
version = "0.2.169"
27+
2528
[target.'cfg(windows)'.dependencies]
2629
windows = { version = "0.59.0", features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_SystemServices"] }
2730

examples/reflink_block.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@ fn main() -> std::io::Result<()> {
2121
to_file.set_len(len)?;
2222

2323
let mut offset = 0u64;
24-
while offset < len as u64 {
25-
println!("reflink {offset}, {cluster_size}");
26-
reflink_copy::ReflinkBlockBuilder::new(&from_file, &to_file, cluster_size)
24+
while offset < len {
25+
let src_length = if cfg!(windows) {
26+
// Windows API clones the entire cluster regardless of the number of bytes actually used
27+
// by the file in that cluster.
28+
cluster_size
29+
} else {
30+
cluster_size.min(NonZeroU64::new(len - offset).unwrap())
31+
};
32+
33+
println!("reflink {offset}, {src_length}");
34+
reflink_copy::ReflinkBlockBuilder::new(&from_file, &to_file, src_length)
2735
.from_offset(offset)
2836
.to_offset(offset)
2937
.cluster_size(cluster_size)
3038
.reflink_block()?;
3139

32-
offset += cluster_size.get();
40+
offset += src_length.get();
3341
}
3442
Ok(())
3543
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ pub fn reflink_or_copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Resu
168168
/// Ok(())
169169
/// }
170170
/// ```
171+
#[cfg_attr(not(windows), allow(unused_variables))]
171172
pub fn check_reflink_support(
172173
from: impl AsRef<Path>,
173174
to: impl AsRef<Path>,

src/reflink_block.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,28 @@ use std::num::NonZeroU64;
1111
/// If you need to clone an entire file, consider using the [`reflink`] or [`reflink_or_copy`]
1212
/// functions instead.
1313
///
14-
/// > Note: Currently the function works only for windows. It returns `Err` for any other platform.
14+
/// > Note: Currently the function works only for windows and linux platforms. It returns `Err` for
15+
/// any other platform.
1516
///
1617
/// # General restrictions
18+
///
1719
/// - The source and destination regions must begin and end at a cluster boundary.
1820
/// - If the source and destination regions are in the same file, they must not overlap. (The
1921
/// application may able to proceed by splitting up the block clone operation into multiple block
2022
/// clones that no longer overlap.)
23+
/// - `src_length` equal to 0 is not supported.
24+
///
25+
/// # Linux specific restrictions and remarks
26+
///
27+
/// - If the file size is not aligned to the cluster size, the reflink operation must not exceed
28+
/// the file length. For example, to reflink the whole file with size of 7000 bytes, `src_length`
29+
/// should be 7000 bytes.
30+
///
31+
/// More information about block cloning on Linux can be found by the
32+
/// [link](https://www.man7.org/linux/man-pages/man2/ioctl_ficlonerange.2.html).
2133
///
2234
/// # Windows specific restrictions and remarks
35+
///
2336
/// - The destination region must not extend past the end of file. If the application wishes to
2437
/// extend the destination with cloned data, it must first call
2538
/// [`File::set_len`](fn@std::fs::File::set_len).
@@ -32,6 +45,9 @@ use std::num::NonZeroU64;
3245
/// - The ReFS volume must have been formatted with Windows Server 2016, and if Windows Failover
3346
/// Clustering is in use, the Clustering Functional Level must have been Windows Server 2016 or
3447
/// later at format time.
48+
/// - If the file size is not aligned to the cluster size, the reflink operation should still
49+
/// be aligned by the cluster size. For example, to reflink the whole file with size of 7000 bytes
50+
/// and a cluster size of 4096 bytes, `src_length` should be 8192 bytes.
3551
///
3652
/// > Note: In order to handle blocks larger than 4GB,
3753
/// [`ReflinkBlockBuilder::reflink_block`] splits these big blocks into smaller ones.
@@ -117,18 +133,14 @@ impl<'from, 'to> ReflinkBlockBuilder<'from, 'to> {
117133
}
118134

119135
/// Performs reflink operation for the specified block of data.
120-
#[cfg_attr(not(windows), allow(unused_variables))]
121136
pub fn reflink_block(self) -> io::Result<()> {
122-
#[cfg(windows)]
123-
return sys::reflink_block(
137+
sys::reflink_block(
124138
self.from,
125139
self.from_offset,
126140
self.to,
127141
self.to_offset,
128142
self.src_length,
129143
self.cluster_size,
130-
);
131-
#[cfg(not(windows))]
132-
Err(io::Error::other("Not implemented"))
144+
)
133145
}
134146
}

src/sys/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::path::Path;
2+
use std::{fs, io};
23

34
use cfg_if::cfg_if;
45

@@ -8,17 +9,31 @@ cfg_if! {
89
if #[cfg(unix)] {
910
mod unix;
1011
pub use self::unix::reflink;
12+
pub(crate) use self::unix::reflink_block;
1113
} else if #[cfg(windows)] {
1214
mod windows_impl;
1315
pub use self::windows_impl::reflink;
1416
pub use self::windows_impl::check_reflink_support;
1517
pub(crate) use self::windows_impl::reflink_block;
1618
} else {
1719
pub use self::reflink_not_supported as reflink;
20+
pub(crate) use self::reflink_block_not_supported as reflink_block;
1821
}
1922
}
2023

2124
#[allow(dead_code)]
2225
pub fn reflink_not_supported(_from: &Path, _to: &Path) -> std::io::Result<()> {
2326
Err(std::io::ErrorKind::Unsupported.into())
2427
}
28+
29+
#[allow(dead_code)]
30+
pub(crate) fn reflink_block_not_supported(
31+
_from: &fs::File,
32+
_from_offset: u64,
33+
_to: &fs::File,
34+
_to_offset: u64,
35+
_src_length: u64,
36+
_cluster_size: Option<std::num::NonZeroU64>,
37+
) -> io::Result<()> {
38+
Err(std::io::ErrorKind::Unsupported.into())
39+
}

src/sys/unix/linux.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::convert::TryInto;
2+
use std::os::unix::io::AsRawFd;
13
use std::{fs, io, path::Path};
24

35
use crate::sys::utility::AutoRemovedFile;
@@ -12,3 +14,35 @@ pub fn reflink(from: &Path, to: &Path) -> io::Result<()> {
1214
dest.persist();
1315
Ok(())
1416
}
17+
18+
#[cfg(target_os = "linux")]
19+
pub(crate) fn reflink_block(
20+
from: &fs::File,
21+
from_offset: u64,
22+
to: &fs::File,
23+
to_offset: u64,
24+
src_length: u64,
25+
_cluster_size: Option<std::num::NonZeroU64>,
26+
) -> io::Result<()> {
27+
let ret = unsafe {
28+
libc::ioctl(
29+
to.as_raw_fd(),
30+
libc::FICLONERANGE.try_into().unwrap(),
31+
&libc::file_clone_range {
32+
src_fd: from.as_raw_fd().into(),
33+
src_offset: from_offset,
34+
src_length,
35+
dest_offset: to_offset,
36+
},
37+
)
38+
};
39+
40+
if ret == -1 {
41+
Err(io::Error::last_os_error())
42+
} else {
43+
Ok(())
44+
}
45+
}
46+
47+
#[cfg(target_os = "android")]
48+
pub(crate) use crate::sys::reflink_block_not_supported as reflink_block;

src/sys/unix/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ cfg_if! {
44
if #[cfg(any(target_os = "linux", target_os = "android"))] {
55
mod linux;
66
pub use linux::reflink;
7+
pub(crate) use linux::reflink_block;
78
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] {
89
mod macos;
910
pub use macos::reflink;
11+
pub(crate) use super::reflink_block_not_supported as reflink_block;
1012
} else {
1113
pub use super::reflink_not_supported as reflink;
14+
pub(crate) use super::reflink_block_not_supported as reflink_block;
1215
}
1316
}

0 commit comments

Comments
 (0)