fd/splice.rs
1// Copyright (C) 2015-2017 Mickaël Salaün
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Lesser General Public License as published by
5// the Free Software Foundation, version 3 of the License.
6//
7// This program is distributed in the hope that it will be useful,
8// but WITHOUT ANY WARRANTY; without even the implied warranty of
9// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10// GNU Lesser General Public License for more details.
11//
12// You should have received a copy of the GNU Lesser General Public License
13// along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15use libc::{size_t, ssize_t};
16use std::{io, ptr};
17use std::os::unix::io::RawFd;
18use std::sync::Arc;
19use std::sync::atomic::AtomicBool;
20use std::sync::atomic::Ordering::Relaxed;
21use std::sync::mpsc::Sender;
22
23mod raw {
24 use std::os::unix::io::RawFd;
25 use ::libc::{c_longlong, size_t, ssize_t, c_uint};
26
27 pub use ::libc::SPLICE_F_NONBLOCK;
28
29 // From asm-generic/posix_types.h
30 #[allow(non_camel_case_types)]
31 pub type loff_t = c_longlong;
32
33 extern {
34 pub fn splice(fd_in: RawFd, off_in: *mut loff_t, fd_out: RawFd, off_out: *mut loff_t,
35 len: size_t, flags: c_uint) -> ssize_t;
36 }
37}
38
39enum SpliceMode {
40 Block,
41 #[allow(dead_code)]
42 NonBlock
43}
44
45// TODO: Replace most &RawFd with AsRawFd
46fn splice(fd_in: &RawFd, fd_out: &RawFd, len: size_t, mode: SpliceMode) -> io::Result<ssize_t> {
47 let flags = match mode {
48 SpliceMode::Block => 0,
49 SpliceMode::NonBlock => raw::SPLICE_F_NONBLOCK,
50 };
51 match unsafe { raw::splice(*fd_in, ptr::null_mut(), *fd_out, ptr::null_mut(), len, flags) } {
52 -1 => Err(io::Error::last_os_error()),
53 s => Ok(s),
54 }
55}
56
57static SPLICE_BUFFER_SIZE: size_t = 1024;
58
59/// Loop while reading and writing from one file descriptor to another using `splice(2)`.
60/// The loop stop when `do_flush` is set to `true`.
61/// At the end, a flush event is send to `flush_event` if any.
62///
63/// This function should be used in a dedicated thread, e.g. `thread::spawn(move ||
64/// splice_loop(do_flush, None, rx.as_raw_fd(), tx.as_raw_fd()))`.
65///
66/// You should ensure that there is no append flag to the `fd_out` file descriptor.
67/// You can use `unset_append_flag()` if needed and `set_flags()` to restore to the initial state.
68pub fn splice_loop(do_flush: Arc<AtomicBool>, flush_event: Option<Sender<()>>, fd_in: RawFd, fd_out: RawFd) {
69 'select: loop {
70 if do_flush.load(Relaxed) {
71 break 'select;
72 }
73 // FIXME: Add a select(2) watching for stdin and a pipe to stop the task
74 // Need pipe to block on (the kernel only look at input)
75 match splice(&fd_in, &fd_out, SPLICE_BUFFER_SIZE, SpliceMode::Block) {
76 Ok(..) => {},
77 Err(e) => {
78 match e.kind() {
79 io::ErrorKind::BrokenPipe => {},
80 _ => {
81 do_flush.store(true, Relaxed);
82 break 'select;
83 }
84 }
85 }
86 }
87 }
88 match flush_event {
89 Some(event) => {
90 let _ = event.send(());
91 },
92 None => {}
93 }
94}