use crate::{
error::UResult,
pipes::{pipe, splice, splice_exact},
};
use std::{
io::{Read, Write},
os::fd::{AsFd, AsRawFd},
};
use super::common::Error;
pub trait FdReadable: Read + AsRawFd + AsFd {}
impl<T> FdReadable for T where T: Read + AsFd + AsRawFd {}
pub trait FdWritable: Write + AsFd + AsRawFd {}
impl<T> FdWritable for T where T: Write + AsFd + AsRawFd {}
const SPLICE_SIZE: usize = 1024 * 128;
const BUF_SIZE: usize = 1024 * 16;
impl From<nix::Error> for Error {
fn from(error: nix::Error) -> Self {
Self::Io(std::io::Error::from_raw_os_error(error as i32))
}
}
pub fn copy_stream<R, S>(src: &mut R, dest: &mut S) -> UResult<u64>
where
R: Read + AsFd + AsRawFd,
S: Write + AsFd + AsRawFd,
{
let result = splice_write(src, &dest.as_fd())?;
if !result.1 {
return Ok(result.0);
}
let result = std::io::copy(src, dest)?;
dest.flush()?;
Ok(result)
}
#[inline]
pub(crate) fn splice_write<R, S>(source: &R, dest: &S) -> UResult<(u64, bool)>
where
R: Read + AsFd + AsRawFd,
S: AsRawFd + AsFd,
{
let (pipe_rd, pipe_wr) = pipe()?;
let mut bytes: u64 = 0;
loop {
match splice(&source, &pipe_wr, SPLICE_SIZE) {
Ok(n) => {
if n == 0 {
return Ok((bytes, false));
}
if splice_exact(&pipe_rd, dest, n).is_err() {
copy_exact(&pipe_rd, dest, n)?;
return Ok((bytes, true));
}
bytes += n as u64;
}
Err(_) => {
return Ok((bytes, true));
}
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn copy_exact(
read_fd: &impl AsFd,
write_fd: &impl AsFd,
num_bytes: usize,
) -> std::io::Result<usize> {
use nix::unistd;
let mut left = num_bytes;
let mut buf = [0; BUF_SIZE];
let mut written = 0;
while left > 0 {
let read = unistd::read(read_fd, &mut buf)?;
assert_ne!(read, 0, "unexpected end of pipe");
while written < read {
let n = unistd::write(write_fd, &buf[written..read])?;
written += n;
}
left -= read;
}
Ok(written)
}