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)
}