Skip to content

Commit

Permalink
Implement copy_file_range()
Browse files Browse the repository at this point in the history
  • Loading branch information
Árni Dagur authored and Árni Dagur committed Jun 12, 2019
1 parent 50f55e7 commit 0f19012
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#1079](https://github.com/nix-rust/nix/pull/1079))
- Implemented `Clone`, `Copy`, `Debug`, `Eq`, `Hash`, and `PartialEq` for most
types that support them. ([#1035](https://github.com/nix-rust/nix/pull/1035))
- Added `copy_file_range` wrapper
([#1069](https://github.com/nix-rust/nix/pull/1069))

### Changed
- Support for `ifaddrs` now present when building for Android.
Expand Down
73 changes: 65 additions & 8 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::os::unix::io::RawFd;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;

#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr; // For splice and copy_file_range
#[cfg(any(target_os = "android", target_os = "linux"))]
use sys::uio::IoVec; // For vmsplice

Expand Down Expand Up @@ -325,15 +327,70 @@ libc_bitflags! {
}
}

/// Copy a range of data from one file to another
///
/// The `copy_file_range` system call performs an in-kernel copy between
/// file descriptors `fd_in` and `fd_out` without the additional cost of
/// transferring data from the kernel to user space and then back into the
/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to
/// file descriptor `fd_out`, overwriting any data that exists within the
/// requested range of the target file.
///
/// If the `off_in` and/or `off_out` arguments are used, the values
/// will be mutated to reflect the new position within the file after
/// copying. If they are not used, the relevant filedescriptors will be seeked
/// to the new position.
///
/// On successful completion the number of bytes actually copied will be
/// returned.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn copy_file_range(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());

let ret = unsafe {
libc::syscall(
libc::SYS_copy_file_range,
fd_in,
off_in,
fd_out,
off_out,
len,
0,
)
};
Errno::result(ret).map(|r| r as usize)
}

#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn splice(fd_in: RawFd, off_in: Option<&mut libc::loff_t>,
fd_out: RawFd, off_out: Option<&mut libc::loff_t>,
len: usize, flags: SpliceFFlags) -> Result<usize> {
use std::ptr;
let off_in = off_in.map(|offset| offset as *mut _).unwrap_or(ptr::null_mut());
let off_out = off_out.map(|offset| offset as *mut _).unwrap_or(ptr::null_mut());

let ret = unsafe { libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) };
pub fn splice(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
flags: SpliceFFlags,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());

let ret = unsafe {
libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits())
};
Errno::result(ret).map(|r| r as usize)
}

Expand Down
41 changes: 40 additions & 1 deletion test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,55 @@ fn test_readlink() {
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux_android {
use std::io::prelude::*;
use std::io::SeekFrom;
use std::os::unix::prelude::*;

use libc::loff_t;

use nix::fcntl::{SpliceFFlags, FallocateFlags, fallocate, splice, tee, vmsplice};
use nix::fcntl::*;
use nix::sys::uio::IoVec;
use nix::unistd::{close, pipe, read, write};

use tempfile::{tempfile, NamedTempFile};

/// This test creates a temporary file containing the contents
/// 'foobarbaz' and uses the `copy_file_range` call to transfer
/// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
/// resulting file is read and should contain the contents `bar`.
/// The from_offset should be updated by the call to reflect
/// the 3 bytes read (6).
///
/// FIXME: This test is disabled for linux based builds, because Travis
/// Linux version is too old for `copy_file_range`.
#[test]
#[ignore]
fn test_copy_file_range() {
const CONTENTS: &[u8] = b"foobarbaz";

let mut tmp1 = tempfile().unwrap();
let mut tmp2 = tempfile().unwrap();

tmp1.write_all(CONTENTS).unwrap();
tmp1.flush().unwrap();

let mut from_offset: i64 = 3;
copy_file_range(
tmp1.as_raw_fd(),
Some(&mut from_offset),
tmp2.as_raw_fd(),
None,
3,
)
.unwrap();

let mut res: String = String::new();
tmp2.seek(SeekFrom::Start(0)).unwrap();
tmp2.read_to_string(&mut res).unwrap();

assert_eq!(res, String::from("bar"));
assert_eq!(from_offset, 6);
}

#[test]
fn test_splice() {
const CONTENTS: &[u8] = b"abcdef123456";
Expand Down

0 comments on commit 0f19012

Please sign in to comment.