Skip to main content

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::{AsFd, AsRawFd};
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) for Linux,
24/// see [the sendfile(2) man page.](https://docs.oracle.com/cd/E88353_01/html/E37843/sendfile-3c.html) for Solaris.
25#[cfg(any(linux_android, solarish))]
26pub fn sendfile<F1: AsFd, F2: AsFd>(
27    out_fd: F1,
28    in_fd: F2,
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 {
36        libc::sendfile(
37            out_fd.as_fd().as_raw_fd(),
38            in_fd.as_fd().as_raw_fd(),
39            offset,
40            count,
41        )
42    };
43    Errno::result(ret).map(|r| r as usize)
44}
45
46/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
47///
48/// Returns a `Result` with the number of bytes written.
49///
50/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will
51/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified
52/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to
53/// the byte after the last byte copied.
54///
55/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket.
56///
57/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html)
58#[cfg(target_os = "linux")]
59pub fn sendfile64<F1: AsFd, F2: AsFd>(
60    out_fd: F1,
61    in_fd: F2,
62    offset: Option<&mut libc::off64_t>,
63    count: usize,
64) -> Result<usize> {
65    let offset = offset
66        .map(|offset| offset as *mut _)
67        .unwrap_or(ptr::null_mut());
68    let ret = unsafe {
69        libc::sendfile64(
70            out_fd.as_fd().as_raw_fd(),
71            in_fd.as_fd().as_raw_fd(),
72            offset,
73            count,
74        )
75    };
76    Errno::result(ret).map(|r| r as usize)
77}
78
79cfg_if! {
80    if #[cfg(any(freebsdlike, apple_targets))] {
81        use std::io::IoSlice;
82
83        #[derive(Clone, Debug)]
84        struct SendfileHeaderTrailer<'a> {
85            raw: libc::sf_hdtr,
86            _headers: Option<Vec<IoSlice<'a>>>,
87            _trailers: Option<Vec<IoSlice<'a>>>,
88        }
89
90        impl<'a> SendfileHeaderTrailer<'a> {
91            fn new(
92                headers: Option<&'a [&'a [u8]]>,
93                trailers: Option<&'a [&'a [u8]]>
94            ) -> SendfileHeaderTrailer<'a> {
95                let mut header_iovecs: Option<Vec<IoSlice<'_>>> =
96                    headers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect());
97                let mut trailer_iovecs: Option<Vec<IoSlice<'_>>> =
98                    trailers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect());
99
100                SendfileHeaderTrailer {
101                    raw: libc::sf_hdtr {
102                        headers: {
103                            header_iovecs
104                                .as_mut()
105                                .map_or(ptr::null_mut(), |v| v.as_mut_ptr())
106                                .cast()
107                        },
108                        hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32,
109                        trailers: {
110                            trailer_iovecs
111                                .as_mut()
112                                .map_or(ptr::null_mut(), |v| v.as_mut_ptr())
113                                .cast()
114                        },
115                        trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32
116                    },
117                    _headers: header_iovecs,
118                    _trailers: trailer_iovecs,
119                }
120            }
121        }
122    } else if #[cfg(solarish)] {
123        use std::os::unix::io::BorrowedFd;
124        use std::marker::PhantomData;
125
126        #[derive(Debug, Copy, Clone)]
127        /// Mapping of the raw C sendfilevec_t struct
128        pub struct SendfileVec<'fd> {
129            raw: libc::sendfilevec_t,
130            phantom: PhantomData<BorrowedFd<'fd>>
131        }
132
133        impl<'fd> SendfileVec<'fd> {
134            /// initialises SendfileVec to send data directly from the process's address space
135            /// same in C with sfv_fd set to SFV_FD_SELF.
136            pub fn newself(
137                off: off_t,
138                len: usize
139            ) -> Self {
140                Self{raw: libc::sendfilevec_t{sfv_fd: libc::SFV_FD_SELF, sfv_flag: 0, sfv_off: off, sfv_len: len}, phantom: PhantomData}
141            }
142
143            /// initialises SendfileVec to send data from `fd`.
144            pub fn new(
145                fd: BorrowedFd<'fd>,
146                off: off_t,
147                len: usize
148            ) -> SendfileVec<'fd> {
149                Self{raw: libc::sendfilevec_t{sfv_fd: fd.as_raw_fd(), sfv_flag: 0, sfv_off:off, sfv_len: len}, phantom: PhantomData}
150            }
151        }
152
153        impl From<SendfileVec<'_>> for libc::sendfilevec_t {
154            fn from<'fd>(vec: SendfileVec) -> libc::sendfilevec_t {
155                vec.raw
156            }
157        }
158    }
159}
160
161cfg_if! {
162    if #[cfg(target_os = "freebsd")] {
163        use libc::c_int;
164
165        libc_bitflags!{
166            /// Configuration options for [`sendfile`.](fn.sendfile.html)
167            pub struct SfFlags: c_int {
168                /// Causes `sendfile` to return EBUSY instead of blocking when attempting to read a
169                /// busy page.
170                SF_NODISKIO;
171                /// Causes `sendfile` to sleep until the network stack releases its reference to the
172                /// VM pages read. When `sendfile` returns, the data is not guaranteed to have been
173                /// sent, but it is safe to modify the file.
174                SF_SYNC;
175                /// Causes `sendfile` to cache exactly the number of pages specified in the
176                /// `readahead` parameter, disabling caching heuristics.
177                SF_USER_READAHEAD;
178                /// Causes `sendfile` not to cache the data read.
179                SF_NOCACHE;
180            }
181        }
182
183        /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`.
184        ///
185        /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
186        /// an error occurs.
187        ///
188        /// `in_fd` must describe a regular file or shared memory object. `out_sock` must describe a
189        /// stream socket.
190        ///
191        /// If `offset` falls past the end of the file, the function returns success and zero bytes
192        /// written.
193        ///
194        /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
195        /// file (EOF).
196        ///
197        /// `headers` and `trailers` specify optional slices of byte slices to be sent before and
198        /// after the data read from `in_fd`, respectively. The length of headers and trailers sent
199        /// is included in the returned count of bytes written. The values of `offset` and `count`
200        /// do not apply to headers or trailers.
201        ///
202        /// `readahead` specifies the minimum number of pages to cache in memory ahead of the page
203        /// currently being sent.
204        ///
205        /// For more information, see
206        /// [the sendfile(2) man page.](https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2)
207        #[allow(clippy::too_many_arguments)]
208        pub fn sendfile<F1: AsFd, F2: AsFd>(
209            in_fd: F1,
210            out_sock: F2,
211            offset: off_t,
212            count: Option<usize>,
213            headers: Option<&[&[u8]]>,
214            trailers: Option<&[&[u8]]>,
215            flags: SfFlags,
216            readahead: u16
217        ) -> (Result<()>, off_t) {
218            // Readahead goes in upper 16 bits
219            // Flags goes in lower 16 bits
220            // see `man 2 sendfile`
221            let ra32 = u32::from(readahead);
222            let flags: u32 = (ra32 << 16) | (flags.bits() as u32);
223            let mut bytes_sent: off_t = 0;
224            let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
225            let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
226            let return_code = unsafe {
227                libc::sendfile(in_fd.as_fd().as_raw_fd(),
228                               out_sock.as_fd().as_raw_fd(),
229                               offset,
230                               count.unwrap_or(0),
231                               hdtr_ptr as *mut libc::sf_hdtr,
232                               &mut bytes_sent as *mut off_t,
233                               flags as c_int)
234            };
235            (Errno::result(return_code).and(Ok(())), bytes_sent)
236        }
237    } else if #[cfg(target_os = "dragonfly")] {
238        /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`.
239        ///
240        /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
241        /// an error occurs.
242        ///
243        /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket.
244        ///
245        /// If `offset` falls past the end of the file, the function returns success and zero bytes
246        /// written.
247        ///
248        /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
249        /// file (EOF).
250        ///
251        /// `headers` and `trailers` specify optional slices of byte slices to be sent before and
252        /// after the data read from `in_fd`, respectively. The length of headers and trailers sent
253        /// is included in the returned count of bytes written. The values of `offset` and `count`
254        /// do not apply to headers or trailers.
255        ///
256        /// For more information, see
257        /// [the sendfile(2) man page.](https://leaf.dragonflybsd.org/cgi/web-man?command=sendfile&section=2)
258        pub fn sendfile<F1: AsFd, F2: AsFd>(
259            in_fd: F1,
260            out_sock: F2,
261            offset: off_t,
262            count: Option<usize>,
263            headers: Option<&[&[u8]]>,
264            trailers: Option<&[&[u8]]>,
265        ) -> (Result<()>, off_t) {
266            let mut bytes_sent: off_t = 0;
267            let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
268            let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
269            let return_code = unsafe {
270                libc::sendfile(in_fd.as_fd().as_raw_fd(),
271                               out_sock.as_fd().as_raw_fd(),
272                               offset,
273                               count.unwrap_or(0),
274                               hdtr_ptr as *mut libc::sf_hdtr,
275                               &mut bytes_sent as *mut off_t,
276                               0)
277            };
278            (Errno::result(return_code).and(Ok(())), bytes_sent)
279        }
280    } else if #[cfg(apple_targets)] {
281        /// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to
282        /// `out_sock`.
283        ///
284        /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
285        /// an error occurs.
286        ///
287        /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket.
288        ///
289        /// If `offset` falls past the end of the file, the function returns success and zero bytes
290        /// written.
291        ///
292        /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
293        /// file (EOF).
294        ///
295        /// `hdtr` specifies an optional list of headers and trailers to be sent before and after
296        /// the data read from `in_fd`, respectively. The length of headers and trailers sent is
297        /// included in the returned count of bytes written. If any headers are specified and
298        /// `count` is non-zero, the length of the headers will be counted in the limit of total
299        /// bytes sent. Trailers do not count toward the limit of bytes sent and will always be sent
300        /// regardless. The value of `offset` does not affect headers or trailers.
301        ///
302        /// For more information, see
303        /// [the sendfile(2) man page.](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/sendfile.2.html)
304        pub fn sendfile<F1: AsFd, F2: AsFd>(
305            in_fd: F1,
306            out_sock: F2,
307            offset: off_t,
308            count: Option<off_t>,
309            headers: Option<&[&[u8]]>,
310            trailers: Option<&[&[u8]]>
311        ) -> (Result<()>, off_t) {
312            let mut len = count.unwrap_or(0);
313            let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
314            let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
315            let return_code = unsafe {
316                libc::sendfile(in_fd.as_fd().as_raw_fd(),
317                               out_sock.as_fd().as_raw_fd(),
318                               offset,
319                               &mut len as *mut off_t,
320                               hdtr_ptr as *mut libc::sf_hdtr,
321                               0)
322            };
323            (Errno::result(return_code).and(Ok(())), len)
324        }
325    } else if #[cfg(solarish)] {
326        /// Write data from the vec arrays to `out_sock` and returns a `Result` and a
327        /// count of bytes written.
328        ///
329        /// Each `SendfileVec` set needs to be instantiated either with `SendfileVec::new` or
330        /// `SendfileVec::newself`.
331        ///
332        /// The former allows to send data from a file descriptor through `fd`,
333        ///  from an offset `off` and for a given amount of data `len`.
334        ///
335        /// The latter allows to send data from the process's address space, from an offset `off`
336        /// and for a given amount of data `len`.
337        ///
338        /// For more information, see
339        /// [the sendfilev(3) man page.](https://illumos.org/man/3EXT/sendfilev)
340        pub fn sendfilev<F: AsFd>(
341            out_sock: F,
342            vec: &[SendfileVec]
343        ) -> (Result<()>, usize) {
344            let mut len = 0usize;
345            let return_code = unsafe {
346                libc::sendfilev(out_sock.as_fd().as_raw_fd(), vec.as_ptr() as *const libc::sendfilevec_t, vec.len() as i32, &mut len)
347            };
348            (Errno::result(return_code).and(Ok(())), len)
349        }
350    }
351}