Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/backend/linux_raw/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub(crate) use linux_raw_sys::general::{
XATTR_REPLACE,
};

pub(crate) use linux_raw_sys::ioctl::{BLKPBSZGET, BLKSSZGET, FICLONE};
pub(crate) use linux_raw_sys::ioctl::{BLKPBSZGET, BLKSSZGET, FICLONE, FICLONERANGE};
#[cfg(target_pointer_width = "32")]
pub(crate) use linux_raw_sys::ioctl::{FS_IOC32_GETFLAGS, FS_IOC32_SETFLAGS};
#[cfg(target_pointer_width = "64")]
Expand Down
67 changes: 67 additions & 0 deletions src/fs/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#![allow(unsafe_code)]

#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
use std::{marker::PhantomData, os::fd::OwnedFd};

#[cfg(linux_kernel)]
use {
crate::backend::c,
Expand Down Expand Up @@ -57,6 +60,38 @@ pub fn ioctl_ficlone<Fd: AsFd, SrcFd: AsFd>(fd: Fd, src_fd: SrcFd) -> io::Result
unsafe { ioctl::ioctl(fd, Ficlone(src_fd.as_fd())) }
}

/// `ioctl(fd, FICLONERANGE, ...)`—share some the data of one file with another file.
///
/// This ioctl is not available on SPARC platforms.
///
/// # References
/// - [Linux]
///
/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl_ficlonerange.2.html
#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
#[inline]
#[doc(alias = "FICLONERANGE")]
pub fn ioctl_ficlonerange<Fd: AsFd, SrcFd: AsFd>(
fd: Fd,
src_fd: SrcFd,
src_offset: u64,
src_length: u64,
dest_offset: u64,
) -> io::Result<()> {
unsafe {
ioctl::ioctl(
fd,
Ficlonerange {
src_fd: i64::from(src_fd.as_fd().as_raw_fd()),
src_offset,
src_length,
dest_offset,
_phantom: PhantomData,
},
)
}
}

/// `ioctl(fd, EXT4_IOC_RESIZE_FS, blocks)`—Resize ext4 filesystem on fd.
#[cfg(linux_raw_dep)]
#[inline]
Expand Down Expand Up @@ -94,6 +129,38 @@ unsafe impl ioctl::Ioctl for Ficlone<'_> {
}
}

#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
#[repr(C)]
struct Ficlonerange<'a> {
src_fd: i64,
src_offset: u64,
src_length: u64,
dest_offset: u64,
_phantom: PhantomData<&'a OwnedFd>,
}

#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
unsafe impl ioctl::Ioctl for Ficlonerange<'_> {
type Output = ();

const IS_MUTATING: bool = false;

fn opcode(&self) -> ioctl::Opcode {
c::FICLONERANGE as ioctl::Opcode
}

fn as_ptr(&mut self) -> *mut c::c_void {
std::ptr::from_mut(self) as *mut c::c_void
}

unsafe fn output_from_ptr(
_: ioctl::IoctlOutput,
_: *mut c::c_void,
) -> io::Result<Self::Output> {
Ok(())
}
}

#[cfg(linux_raw_dep)]
bitflags! {
/// `FS_*` constants for use with [`ioctl_getflags`].
Expand Down
57 changes: 57 additions & 0 deletions tests/fs/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,60 @@ fn test_ioctl_ficlone() {
Err(err) => panic!("{:?}", err),
}
}

#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
#[test]
fn test_ioctl_ficlonerange() {
use rustix::io;

let src = std::fs::File::open("Cargo.toml").unwrap();
let dest = tempfile::tempfile().unwrap();

// Often the temporary directory is on a different filesystem (like tmpfs),
// which means the test to clone some data doesn't do anything interesting,
// singe the ioctl simply returns failure.
// Uncomment the line below to use a file in the same directory as the source,
// which guarantees they are on the same filesystem.
// let dest = std::fs::File::options()
// .create(true)
// .truncate(true)
// .read(true)
// .write(true)
// .open("test_ficlonerange").unwrap();

let dir = tempfile::tempdir().unwrap();
let dir = std::fs::File::open(dir.path()).unwrap();

// `src` isn't opened for writing, so passing it as the output fails.
assert_eq!(
rustix::fs::ioctl_ficlonerange(&src, &src, 0, 4096, 0),
Err(io::Errno::BADF)
);

// `FICLONERANGE` operates on regular files, not directories.
assert_eq!(
rustix::fs::ioctl_ficlonerange(&dir, &dir, 0, 4096, 0),
Err(io::Errno::ISDIR)
);

// Now try something that might succeed, though be prepared for filesystems
// that don't support this.
// Copy 4096 bytes from offset 4096 in src to offset 8192 in dest.
match rustix::fs::ioctl_ficlonerange(&dest, &src, 4096, 4096, 8192) {
Ok(()) => {
use std::os::unix::fs::FileExt;

let mut expected_buf = vec![0u8; 4096];
let mut actual_buf = vec![0u8; 4096];
src.read_exact_at(expected_buf.as_mut_slice(), 4096)
.unwrap();
dest.read_exact_at(actual_buf.as_mut_slice(), 8192).unwrap();

assert_eq!(expected_buf, actual_buf);
}

Err(io::Errno::OPNOTSUPP) => (),
Err(e) if e == io::Errno::from_raw_os_error(0x12) => (),
Err(err) => panic!("{:?}", err),
}
}
Loading