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

Add sendfile(2) for DragonFly #1615

Merged
merged 1 commit into from Jan 4, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -29,6 +29,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1564](https://github.com/nix-rust/nix/pull/1564))
- Added POSIX per-process timer support
(#[1622](https://github.com/nix-rust/nix/pull/1622))
- Added `sendfile` on DragonFly.
(#[1615](https://github.com/nix-rust/nix/pull/1615))

### Changed
### Fixed
Expand Down
1 change: 1 addition & 0 deletions src/sys/mod.rs
Expand Up @@ -110,6 +110,7 @@ feature! {
}

#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
Expand Down
46 changes: 45 additions & 1 deletion src/sys/sendfile.rs
Expand Up @@ -64,7 +64,8 @@ pub fn sendfile64(
}

cfg_if! {
if #[cfg(any(target_os = "freebsd",
if #[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos"))] {
use crate::sys::uio::IoVec;
Expand Down Expand Up @@ -184,6 +185,49 @@ cfg_if! {
};
(Errno::result(return_code).and(Ok(())), bytes_sent)
}
} else if #[cfg(target_os = "dragonfly")] {
/// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`.
///
/// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
/// an error occurs.
///
/// `in_fd` must describe a regular file. `out_sock` must describe a stream socket.
///
/// If `offset` falls past the end of the file, the function returns success and zero bytes
/// written.
///
/// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
/// file (EOF).
///
/// `headers` and `trailers` specify optional slices of byte slices to be sent before and
/// after the data read from `in_fd`, respectively. The length of headers and trailers sent
/// is included in the returned count of bytes written. The values of `offset` and `count`
/// do not apply to headers or trailers.
///
/// For more information, see
/// [the sendfile(2) man page.](https://leaf.dragonflybsd.org/cgi/web-man?command=sendfile&section=2)
pub fn sendfile(
in_fd: RawFd,
out_sock: RawFd,
offset: off_t,
count: Option<usize>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>,
) -> (Result<()>, off_t) {
let mut bytes_sent: off_t = 0;
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr);
let return_code = unsafe {
libc::sendfile(in_fd,
out_sock,
offset,
count.unwrap_or(0),
hdtr_ptr as *mut libc::sf_hdtr,
&mut bytes_sent as *mut off_t,
0)
};
(Errno::result(return_code).and(Ok(())), bytes_sent)
}
} else if #[cfg(any(target_os = "ios", target_os = "macos"))] {
/// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to
/// `out_sock`.
Expand Down
1 change: 1 addition & 0 deletions test/test.rs
Expand Up @@ -33,6 +33,7 @@ mod test_pty;
target_os = "linux"))]
mod test_sched;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
Expand Down
47 changes: 46 additions & 1 deletion test/test_sendfile.rs
Expand Up @@ -8,7 +8,7 @@ use tempfile::tempfile;
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
use nix::unistd::{close, pipe, read};
} else if #[cfg(any(target_os = "freebsd", target_os = "ios", target_os = "macos"))] {
} else if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] {
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
}
Expand Down Expand Up @@ -105,6 +105,51 @@ fn test_sendfile_freebsd() {
assert_eq!(expected_string, read_string);
}

#[cfg(target_os = "dragonfly")]
#[test]
fn test_sendfile_dragonfly() {
// Declare the content
let header_strings = vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"];
let body = "Xabcdef123456";
let body_offset = 1;
let trailer_strings = vec!["\n", "Served by Make Believe\n"];

// Write the body to a file
let mut tmp = tempfile().unwrap();
tmp.write_all(body.as_bytes()).unwrap();

// Prepare headers and trailers for sendfile
let headers: Vec<&[u8]> = header_strings.iter().map(|s| s.as_bytes()).collect();
let trailers: Vec<&[u8]> = trailer_strings.iter().map(|s| s.as_bytes()).collect();

// Prepare socket pair
let (mut rd, wr) = UnixStream::pair().unwrap();

// Call the test method
let (res, bytes_written) = sendfile(
tmp.as_raw_fd(),
wr.as_raw_fd(),
body_offset as off_t,
None,
Some(headers.as_slice()),
Some(trailers.as_slice()),
);
assert!(res.is_ok());
wr.shutdown(Shutdown::Both).unwrap();

// Prepare the expected result
let expected_string =
header_strings.concat() + &body[body_offset..] + &trailer_strings.concat();

// Verify the message that was sent
assert_eq!(bytes_written as usize, expected_string.as_bytes().len());

let mut read_string = String::new();
let bytes_read = rd.read_to_string(&mut read_string).unwrap();
assert_eq!(bytes_written as usize, bytes_read);
assert_eq!(expected_string, read_string);
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[test]
fn test_sendfile_darwin() {
Expand Down