Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement copy_file_range() #1069

Merged
merged 1 commit into from
Jun 16, 2019
Merged
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: 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