diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c0cc80cd..16b7992dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/fcntl.rs b/src/fcntl.rs index 6da62d87ba..3d932a5398 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -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 @@ -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 { + 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 { - 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 { + 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) } diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index bcc523bfcc..8d02f147b4 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -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";