Skip to content

Commit

Permalink
Add sendfile(2) for DragonFly
Browse files Browse the repository at this point in the history
  • Loading branch information
rtzoeller committed Dec 25, 2021
1 parent 6c6c576 commit 90467da
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1581](https://github.com/nix-rust/nix/pull/1581))
- Added `sched_setaffinity` and `sched_getaffinity` on DragonFly.
(#[1537](https://github.com/nix-rust/nix/pull/1537))
- 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

0 comments on commit 90467da

Please sign in to comment.