Skip to content

Commit 9304788

Browse files
authored
Merge pull request #1906 from asomers/copy_file_range
Expose copy_file_range on FreeBSD and use I/O Safety
2 parents 6fb3a8f + 5d55d8f commit 9304788

File tree

3 files changed

+112
-63
lines changed

3 files changed

+112
-63
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2727
- Added `SO_RTABLE` for OpenBSD and `SO_ACCEPTFILTER` for FreeBSD/NetBSD to `nix::sys::socket::sockopt`.
2828
([#2085](https://github.com/nix-rust/nix/pull/2085))
2929
- Removed `flock` from `::nix::fcntl` on Solaris. ([#2082](https://github.com/nix-rust/nix/pull/2082))
30+
- Use I/O safety with `copy_file_range`, and expose it on FreeBSD.
31+
(#[1906](https://github.com/nix-rust/nix/pull/1906))
3032

3133
### Changed
3234

@@ -44,6 +46,15 @@ This project adheres to [Semantic Versioning](https://semver.org/).
4446
- `nix::socket` and `nix::select` are now available on Redox.
4547
([#2012](https://github.com/nix-rust/nix/pull/2012))
4648

49+
- Implemented I/O safety. Many public functions argument and return types have
50+
changed:
51+
| Original Type | New Type |
52+
| ------------- | --------------------- |
53+
| AsRawFd | AsFd |
54+
| RawFd | BorrowedFd or OwnedFd |
55+
56+
(#[1906](https://github.com/nix-rust/nix/pull/1906))
57+
4758
### Fixed
4859
- Fix: send `ETH_P_ALL` in htons format
4960
([#1925](https://github.com/nix-rust/nix/pull/1925))

src/fcntl.rs

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ use std::ffi::OsString;
55
use std::os::raw;
66
use std::os::unix::ffi::OsStringExt;
77
use std::os::unix::io::RawFd;
8+
// For splice and copy_file_range
9+
#[cfg(any(
10+
target_os = "android",
11+
target_os = "freebsd",
12+
target_os = "linux"
13+
))]
14+
use std::{
15+
os::unix::io::{AsFd, AsRawFd},
16+
ptr,
17+
};
818

919
#[cfg(feature = "fs")]
1020
use crate::{sys::stat::Mode, NixPath, Result};
11-
#[cfg(any(target_os = "android", target_os = "linux"))]
12-
use std::ptr; // For splice and copy_file_range
1321

1422
#[cfg(any(
1523
target_os = "linux",
@@ -612,44 +620,65 @@ feature! {
612620
///
613621
/// The `copy_file_range` system call performs an in-kernel copy between
614622
/// file descriptors `fd_in` and `fd_out` without the additional cost of
615-
/// transferring data from the kernel to user space and then back into the
616-
/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to
617-
/// file descriptor `fd_out`, overwriting any data that exists within the
618-
/// requested range of the target file.
623+
/// transferring data from the kernel to user space and back again. There may be
624+
/// additional optimizations for specific file systems. It copies up to `len`
625+
/// bytes of data from file descriptor `fd_in` to file descriptor `fd_out`,
626+
/// overwriting any data that exists within the requested range of the target
627+
/// file.
619628
///
620629
/// If the `off_in` and/or `off_out` arguments are used, the values
621630
/// will be mutated to reflect the new position within the file after
622-
/// copying. If they are not used, the relevant filedescriptors will be seeked
631+
/// copying. If they are not used, the relevant file descriptors will be seeked
623632
/// to the new position.
624633
///
625634
/// On successful completion the number of bytes actually copied will be
626635
/// returned.
627-
#[cfg(any(target_os = "android", target_os = "linux"))]
628-
pub fn copy_file_range(
629-
fd_in: RawFd,
630-
off_in: Option<&mut libc::loff_t>,
631-
fd_out: RawFd,
632-
off_out: Option<&mut libc::loff_t>,
636+
// Note: FreeBSD defines the offset argument as "off_t". Linux and Android
637+
// define it as "loff_t". But on both OSes, on all supported platforms, those
638+
// are 64 bits. So Nix uses i64 to make the docs simple and consistent.
639+
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
640+
pub fn copy_file_range<Fd1: AsFd, Fd2: AsFd>(
641+
fd_in: Fd1,
642+
off_in: Option<&mut i64>,
643+
fd_out: Fd2,
644+
off_out: Option<&mut i64>,
633645
len: usize,
634646
) -> Result<usize> {
635647
let off_in = off_in
636-
.map(|offset| offset as *mut libc::loff_t)
648+
.map(|offset| offset as *mut i64)
637649
.unwrap_or(ptr::null_mut());
638650
let off_out = off_out
639-
.map(|offset| offset as *mut libc::loff_t)
651+
.map(|offset| offset as *mut i64)
640652
.unwrap_or(ptr::null_mut());
641653

642-
let ret = unsafe {
643-
libc::syscall(
644-
libc::SYS_copy_file_range,
645-
fd_in,
646-
off_in,
647-
fd_out,
648-
off_out,
649-
len,
650-
0,
651-
)
652-
};
654+
cfg_if::cfg_if! {
655+
if #[cfg(target_os = "freebsd")] {
656+
let ret = unsafe {
657+
libc::copy_file_range(
658+
fd_in.as_fd().as_raw_fd(),
659+
off_in,
660+
fd_out.as_fd().as_raw_fd(),
661+
off_out,
662+
len,
663+
0,
664+
)
665+
};
666+
} else {
667+
// May Linux distros still don't include copy_file_range in their
668+
// libc implementations, so we need to make a direct syscall.
669+
let ret = unsafe {
670+
libc::syscall(
671+
libc::SYS_copy_file_range,
672+
fd_in,
673+
off_in,
674+
fd_out.as_fd().as_raw_fd(),
675+
off_out,
676+
len,
677+
0,
678+
)
679+
};
680+
}
681+
}
653682
Errno::result(ret).map(|r| r as usize)
654683
}
655684

test/test_fcntl.rs

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use std::io::prelude::*;
2626
#[cfg(not(target_os = "redox"))]
2727
use std::os::unix::fs;
2828
#[cfg(not(target_os = "redox"))]
29-
use tempfile::{self, NamedTempFile};
29+
use tempfile::NamedTempFile;
3030

3131
#[test]
3232
#[cfg(not(target_os = "redox"))]
@@ -227,6 +227,51 @@ fn test_readlink() {
227227
);
228228
}
229229

230+
/// This test creates a temporary file containing the contents
231+
/// 'foobarbaz' and uses the `copy_file_range` call to transfer
232+
/// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
233+
/// resulting file is read and should contain the contents `bar`.
234+
/// The from_offset should be updated by the call to reflect
235+
/// the 3 bytes read (6).
236+
#[cfg(any(
237+
target_os = "linux",
238+
// Not available until FreeBSD 13.0
239+
all(target_os = "freebsd", fbsd14),
240+
target_os = "android"
241+
))]
242+
#[test]
243+
// QEMU does not support copy_file_range. Skip under qemu
244+
#[cfg_attr(qemu, ignore)]
245+
fn test_copy_file_range() {
246+
use nix::fcntl::copy_file_range;
247+
use std::os::unix::io::AsFd;
248+
249+
const CONTENTS: &[u8] = b"foobarbaz";
250+
251+
let mut tmp1 = tempfile::tempfile().unwrap();
252+
let mut tmp2 = tempfile::tempfile().unwrap();
253+
254+
tmp1.write_all(CONTENTS).unwrap();
255+
tmp1.flush().unwrap();
256+
257+
let mut from_offset: i64 = 3;
258+
copy_file_range(
259+
tmp1.as_fd(),
260+
Some(&mut from_offset),
261+
tmp2.as_fd(),
262+
None,
263+
3,
264+
)
265+
.unwrap();
266+
267+
let mut res: String = String::new();
268+
tmp2.rewind().unwrap();
269+
tmp2.read_to_string(&mut res).unwrap();
270+
271+
assert_eq!(res, String::from("bar"));
272+
assert_eq!(from_offset, 6);
273+
}
274+
230275
#[cfg(any(target_os = "linux", target_os = "android"))]
231276
mod linux_android {
232277
use libc::loff_t;
@@ -243,42 +288,6 @@ mod linux_android {
243288

244289
use crate::*;
245290

246-
/// This test creates a temporary file containing the contents
247-
/// 'foobarbaz' and uses the `copy_file_range` call to transfer
248-
/// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
249-
/// resulting file is read and should contain the contents `bar`.
250-
/// The from_offset should be updated by the call to reflect
251-
/// the 3 bytes read (6).
252-
#[test]
253-
// QEMU does not support copy_file_range. Skip under qemu
254-
#[cfg_attr(qemu, ignore)]
255-
fn test_copy_file_range() {
256-
const CONTENTS: &[u8] = b"foobarbaz";
257-
258-
let mut tmp1 = tempfile().unwrap();
259-
let mut tmp2 = tempfile().unwrap();
260-
261-
tmp1.write_all(CONTENTS).unwrap();
262-
tmp1.flush().unwrap();
263-
264-
let mut from_offset: i64 = 3;
265-
copy_file_range(
266-
tmp1.as_raw_fd(),
267-
Some(&mut from_offset),
268-
tmp2.as_raw_fd(),
269-
None,
270-
3,
271-
)
272-
.unwrap();
273-
274-
let mut res: String = String::new();
275-
tmp2.rewind().unwrap();
276-
tmp2.read_to_string(&mut res).unwrap();
277-
278-
assert_eq!(res, String::from("bar"));
279-
assert_eq!(from_offset, 6);
280-
}
281-
282291
#[test]
283292
fn test_splice() {
284293
const CONTENTS: &[u8] = b"abcdef123456";

0 commit comments

Comments
 (0)