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}