nix/sys/
sendfile.rs

1//! Send data from a file to a socket, bypassing userland.
2
3use cfg_if::cfg_if;
4use std::os::unix::io::RawFd;
5use std::ptr;
6
7use libc::{self, off_t};
8
9use crate::Result;
10use crate::errno::Errno;
11
12/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
13///
14/// Returns a `Result` with the number of bytes written.
15///
16/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will
17/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified
18/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to
19/// the byte after the last byte copied.
20///
21/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket.
22///
23/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html)
24#[cfg(any(target_os = "android", target_os = "linux"))]
25pub fn sendfile(
26    out_fd: RawFd,
27    in_fd: RawFd,
28    offset: Option<&mut off_t>,
29    count: usize,
30) -> Result<usize> {
31    let offset = offset
32        .map(|offset| offset as *mut _)
33        .unwrap_or(ptr::null_mut());
34    let ret = unsafe { libc::sendfile(out_fd, in_fd, offset, count) };
35    Errno::result(ret).map(|r| r as usize)
36}
37
38/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
39///
40/// Returns a `Result` with the number of bytes written.
41///
42/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will
43/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified
44/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to
45/// the byte after the last byte copied.
46///
47/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket.
48///
49/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html)
50#[cfg(target_os = "linux")]
51pub fn sendfile64(
52    out_fd: RawFd,
53    in_fd: RawFd,
54    offset: Option<&mut libc::off64_t>,
55    count: usize,
56) -> Result<usize> {
57    let offset = offset
58        .map(|offset| offset as *mut _)
59        .unwrap_or(ptr::null_mut());
60    let ret = unsafe { libc::sendfile64(out_fd, in_fd, offset, count) };
61    Errno::result(ret).map(|r| r as usize)
62}
63
64cfg_if! {
65    if #[cfg(any(target_os = "freebsd",
66                 target_os = "ios",
67                 target_os = "macos"))] {
68        use crate::sys::uio::IoVec;
69
70        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
71        struct SendfileHeaderTrailer<'a>(
72            libc::sf_hdtr,
73            Option<Vec<IoVec<&'a [u8]>>>,
74            Option<Vec<IoVec<&'a [u8]>>>,
75        );
76
77        impl<'a> SendfileHeaderTrailer<'a> {
78            fn new(
79                headers: Option<&'a [&'a [u8]]>,
80                trailers: Option<&'a [&'a [u8]]>
81            ) -> SendfileHeaderTrailer<'a> {
82                let header_iovecs: Option<Vec<IoVec<&[u8]>>> =
83                    headers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect());
84                let trailer_iovecs: Option<Vec<IoVec<&[u8]>>> =
85                    trailers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect());
86                SendfileHeaderTrailer(
87                    libc::sf_hdtr {
88                        headers: {
89                            header_iovecs
90                                .as_ref()
91                                .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec
92                        },
93                        hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32,
94                        trailers: {
95                            trailer_iovecs
96                                .as_ref()
97                                .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec
98                        },
99                        trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32
100                    },
101                    header_iovecs,
102                    trailer_iovecs,
103                )
104            }
105        }
106    }
107}
108
109cfg_if! {
110    if #[cfg(target_os = "freebsd")] {
111        use libc::c_int;
112
113        libc_bitflags!{
114            /// Configuration options for [`sendfile`.](fn.sendfile.html)
115            pub struct SfFlags: c_int {
116                /// Causes `sendfile` to return EBUSY instead of blocking when attempting to read a
117                /// busy page.
118                SF_NODISKIO;
119                /// Causes `sendfile` to sleep until the network stack releases its reference to the
120                /// VM pages read. When `sendfile` returns, the data is not guaranteed to have been
121                /// sent, but it is safe to modify the file.
122                SF_SYNC;
123                /// Causes `sendfile` to cache exactly the number of pages specified in the
124                /// `readahead` parameter, disabling caching heuristics.
125                SF_USER_READAHEAD;
126                /// Causes `sendfile` not to cache the data read.
127                SF_NOCACHE;
128            }
129        }
130
131        /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`.
132        ///
133        /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
134        /// an error occurs.
135        ///
136        /// `in_fd` must describe a regular file or shared memory object. `out_sock` must describe a
137        /// stream socket.
138        ///
139        /// If `offset` falls past the end of the file, the function returns success and zero bytes
140        /// written.
141        ///
142        /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
143        /// file (EOF).
144        ///
145        /// `headers` and `trailers` specify optional slices of byte slices to be sent before and
146        /// after the data read from `in_fd`, respectively. The length of headers and trailers sent
147        /// is included in the returned count of bytes written. The values of `offset` and `count`
148        /// do not apply to headers or trailers.
149        ///
150        /// `readahead` specifies the minimum number of pages to cache in memory ahead of the page
151        /// currently being sent.
152        ///
153        /// For more information, see
154        /// [the sendfile(2) man page.](https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2)
155        #[allow(clippy::too_many_arguments)]
156        pub fn sendfile(
157            in_fd: RawFd,
158            out_sock: RawFd,
159            offset: off_t,
160            count: Option<usize>,
161            headers: Option<&[&[u8]]>,
162            trailers: Option<&[&[u8]]>,
163            flags: SfFlags,
164            readahead: u16
165        ) -> (Result<()>, off_t) {
166            // Readahead goes in upper 16 bits
167            // Flags goes in lower 16 bits
168            // see `man 2 sendfile`
169            let ra32 = u32::from(readahead);
170            let flags: u32 = (ra32 << 16) | (flags.bits() as u32);
171            let mut bytes_sent: off_t = 0;
172            let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
173            let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr);
174            let return_code = unsafe {
175                libc::sendfile(in_fd,
176                               out_sock,
177                               offset,
178                               count.unwrap_or(0),
179                               hdtr_ptr as *mut libc::sf_hdtr,
180                               &mut bytes_sent as *mut off_t,
181                               flags as c_int)
182            };
183            (Errno::result(return_code).and(Ok(())), bytes_sent)
184        }
185    } else if #[cfg(any(target_os = "ios", target_os = "macos"))] {
186        /// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to
187        /// `out_sock`.
188        ///
189        /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
190        /// an error occurs.
191        ///
192        /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket.
193        ///
194        /// If `offset` falls past the end of the file, the function returns success and zero bytes
195        /// written.
196        ///
197        /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
198        /// file (EOF).
199        ///
200        /// `hdtr` specifies an optional list of headers and trailers to be sent before and after
201        /// the data read from `in_fd`, respectively. The length of headers and trailers sent is
202        /// included in the returned count of bytes written. If any headers are specified and
203        /// `count` is non-zero, the length of the headers will be counted in the limit of total
204        /// bytes sent. Trailers do not count toward the limit of bytes sent and will always be sent
205        /// regardless. The value of `offset` does not affect headers or trailers.
206        ///
207        /// For more information, see
208        /// [the sendfile(2) man page.](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/sendfile.2.html)
209        pub fn sendfile(
210            in_fd: RawFd,
211            out_sock: RawFd,
212            offset: off_t,
213            count: Option<off_t>,
214            headers: Option<&[&[u8]]>,
215            trailers: Option<&[&[u8]]>
216        ) -> (Result<()>, off_t) {
217            let mut len = count.unwrap_or(0);
218            let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
219            let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr);
220            let return_code = unsafe {
221                libc::sendfile(in_fd,
222                               out_sock,
223                               offset,
224                               &mut len as *mut off_t,
225                               hdtr_ptr as *mut libc::sf_hdtr,
226                               0)
227            };
228            (Errno::result(return_code).and(Ok(())), len)
229        }
230    }
231}