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}