1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
/*! Cross-platform abstractions for the `sendfile()` system call. Natively supported are Linux, android, MacOS, iOS, FreeBSD and DragonFlyBSD, and every other `std`-platform using a fallback. # Implementations All native implementation support a maximum file length of [`off_t::max_value()`]. All implementations handle `WouldBlock` and `Interrupted` errors. ## Linux and android The [`sendfile(2)`][linux] system call is used. The file length is required and the functions fails if `file.metadata()` fails. ## MacOS and optionally iOS The [`sendfile(2)`][macos] system call is used. The file length is only required after the maximum file length has been sent. Note that there are sparse reports of `sendfile()` [being buggy on iOS], so if you prefer to use the fallback for `target_os = "ios"`, disable the `ios-sendfile` feature which is enabled by default. ## FreeBSD and DragonFlyBSD The [`sendfile(2)`][bsd] system call is used. The file length is not required. ## Fallback There are two features to change the fallback behavior: The `fallback-bufreader` feature is enabled by default. It sends the file using [`io::copy()`] after wrapping it in a [`BufReader`]. `Interrupted` errors are handled by the implementation. If the `fallback-buf` feature is enabled, the entire contents of the file are loaded into a `Vec` first, which is then written to the stream at once. Only the first `usize::max_value()` may be transmitted. If both features are disabled the file is transmitted by repeatedly using bare [`io::copy()`] until all bytes have been sent. # Large files If you expected to send files larger than 2 gigabytes from a 32-bit system or files larger than 8192 *peta*bytes from a 64-bit system, enable the `large-files` feature which supports all file sizes up to `u64::max_value()`, and if the files become to large for the native solutions a fallback is used. [linux]: http://man7.org/linux/man-pages/man2/sendfile.2.html [macos]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendfile.2.html [bsd]: https://www.freebsd.org/cgi/man.cgi?query=sendfile [being buggy on iOS]: https://blog.phusion.nl/2015/06/04/the-brokenness-of-the-sendfile-system-call/ [`off_t::max_value()`]: https://docs.rs/libc/0.2/libc/type.off_t.html [`io::copy()`]: https://doc.rust-lang.org/stable/std/io/fn.copy.html [`BufReader`]: https://doc.rust-lang.org/stable/std/io/struct.BufReader.html */ #![deny(missing_docs)] //todo: Remove libc extern crate libc; #[cfg(test)] extern crate tempfile; #[cfg(any(target_os = "linux", target_os = "android"))] #[path = "linux.rs"] mod imp; #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] #[path = "freebsd.rs"] mod imp; #[cfg(any(target_os = "macos", all(target_os = "ios", feature = "ios-sendfile")))] #[path = "macos.rs"] mod imp; mod fallback; #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "macos", all(target_os = "ios", feature = "ios-sendfile"), target_os = "freebsd", target_os = "dragonfly" )))] use fallback as imp; #[cfg(all(feature = "fallback-bufreader", feature = "fallback-buf"))] compile_error!("Only one `fallback-*` feature can enabled"); use std::fs::File; use std::io; use std::net::TcpStream; /// Sends the entire contents of a file to a TCP stream. /// /// The file must be opened for reading. /// /// Depending on the backend, a more efficient method is used which prevents copying all data to userspace. /// See the [module documentation] for more. /// /// This function is optimized in a way that it only needs to be called once on a file and stream. /// Trivial errors are handled and the native `sendfile()` is used as much as possible. /// /// If the file has a length of `0`, this function returns successfully without doing additional work. /// /// This function does not guarantee respecting the file offset, if it already has been changed by using `Seek` or `Read`. /// /// # Example /// /// ``` /// use snedfile::send_file; /// # use std::io; /// # use std::fs::File; /// # use std::net::TcpStream; /// /// // somewhere in a server for static files /// fn serve_static(file: &mut File, stream: &mut TcpStream) -> io::Result<()> { /// send_file(file, stream) /// } /// ``` /// /// [module documentation]: index.html #[inline] pub fn send_file(file: &mut File, stream: &mut TcpStream) -> io::Result<()> { imp::send_file(file, stream) } /// Send a specific amount of bytes from a specific offset within a file. /// /// The amount of bytes successfully sent is returned. /// /// # Implementation notes /// /// If the offset is larger than the implementation can handle, /// an error of type `ErrorKind::InvalidData` is returned. /// /// Exactly one system call is used, if available. /// /// Regardless of the enabled features, /// the fallback of `send_exact()` *always* uses the bare `io::copy()`. /// /// The behaviour is not specified (but not undefined) if the offset goes beyond the end of the file. /// /// No kinds of errors are handled. /// /// # Example /// /// ``` /// use snedfile::send_exact; /// # use std::io; /// # use std::fs::File; /// # use std::net::TcpStream; /// /// fn try_serve_static(mut file: File, mut stream: TcpStream) -> io::Result<u64> { /// let len = file.metadata()?.len(); /// /// // the same as the example from `send_file`, /// // but with less automatic error handling /// send_exact(&mut file, &mut stream, len, 0) /// } /// ``` #[inline] pub fn send_exact( file: &mut File, stream: &mut TcpStream, bytes: u64, offset: u64, ) -> io::Result<u64> { imp::send_exact(file, stream, bytes, offset) }