From 4d5c090fcf9f6f51d4303e903676f2d487d60272 Mon Sep 17 00:00:00 2001 From: Ryan Zoeller Date: Fri, 24 Dec 2021 17:57:20 -0600 Subject: [PATCH] Add sendfile(2) for DragonFly --- CHANGELOG.md | 2 ++ src/sys/mod.rs | 1 + src/sys/sendfile.rs | 46 +++++++++++++++++++++++++++++++++++++++++- test/test.rs | 1 + test/test_sendfile.rs | 47 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b097d6a11..1235a71e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 654a4d87bf..0bd0bc9f88 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -110,6 +110,7 @@ feature! { } #[cfg(any(target_os = "android", + target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "linux", diff --git a/src/sys/sendfile.rs b/src/sys/sendfile.rs index 1a87a6800a..5ec0b52679 100644 --- a/src/sys/sendfile.rs +++ b/src/sys/sendfile.rs @@ -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; @@ -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§ion=2) + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option, + 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`. diff --git a/test/test.rs b/test/test.rs index 83cb46450e..3cac48f77f 100644 --- a/test/test.rs +++ b/test/test.rs @@ -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", diff --git a/test/test_sendfile.rs b/test/test_sendfile.rs index b6559d329b..e56ff12faf 100644 --- a/test/test_sendfile.rs +++ b/test/test_sendfile.rs @@ -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; } @@ -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() {