Skip to content

Commit

Permalink
Adds implementation of copy_file_range()
Browse files Browse the repository at this point in the history
Also adds CHANGELOG.md and tests
  • Loading branch information
edwinbdr authored and Edwin de Jong committed Jan 8, 2019
1 parent 3749858 commit 53c7982
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#972](https://github.com/nix-rust/nix/pull/972))
- Added `symlinkat` wrapper.
([#997](https://github.com/nix-rust/nix/pull/997))
- Added `copy_file_range` wrapper
([#1008](https://github.com/nix-rust/nix/pull/1008))

### Changed
### Fixed
Expand Down
43 changes: 41 additions & 2 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ pub enum FcntlArg<'a> {
}
pub use self::FcntlArg::*;

#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr;

// TODO: Figure out how to handle value fcntl returns
pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
let res = unsafe {
Expand Down Expand Up @@ -305,6 +308,43 @@ pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
Errno::result(res).map(drop)
}


#[cfg(any(target_os = "linux", target_os = "android"))]
bitflags! {
pub struct CopyFileRangeFlags: c_int {
const NONE = 0 as c_int;
}
}

/// Copy a range of data from one file to another
///
/// The `copy_file_range` system call performs an in-kernel copy between
/// file descriptor `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.
///
/// The flags argument is currently unused, and is provided to allow for future
/// extensions and currently must be set to `CopyFileRangeFlags::empty()`.
///
/// On 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,
_flags: CopyFileRangeFlags) -> Result<usize> {

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::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 = "android", target_os = "linux"))]
libc_bitflags! {
/// Additional flags to `splice` and friends.
Expand All @@ -329,8 +369,7 @@ libc_bitflags! {
#[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;
len: usize, flags: SpliceFFlags) -> Result<usize> {
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());

Expand Down
32 changes: 31 additions & 1 deletion test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,42 @@ mod linux_android {

use libc::loff_t;

use nix::fcntl::{SpliceFFlags, FallocateFlags, fallocate, splice, tee, vmsplice};
use nix::fcntl::{SpliceFFlags, FallocateFlags, CopyFileRangeFlags, fallocate, splice, tee, vmsplice, copy_file_range};
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).
#[test]
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: libc::loff_t = 3;
copy_file_range(tmp1.as_raw_fd(), Some(&mut from_offset),
tmp2.as_raw_fd(), None,
3, CopyFileRangeFlags::NONE).unwrap();

tmp2.sync_all().unwrap();
let mut res: String = String::new();
tmp2.seek(std::io::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 53c7982

Please sign in to comment.