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