use core::{mem, ops, ptr};
#[cfg(all(unix, feature = "std-io"))]
use std::{
fs::File,
io::{self, PipeReader, PipeWriter},
os::fd::AsRawFd,
};
#[cfg(all(unix, feature = "std-io"))]
use libc;
use super::BufError;
#[cfg(all(unix, feature = "std-io"))]
use super::{BlockingPipe, NonBlockingPipe};
#[cfg(all(unix, feature = "std-io"))]
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct FilePos(libc::off64_t);
#[cfg(all(unix, feature = "std-io"))]
impl ops::Deref for FilePos {
type Target = libc::off64_t;
#[inline(always)]
fn deref(&self) -> &Self::Target { &self.0 }
}
#[cfg(all(unix, feature = "std-io"))]
impl ops::DerefMut for FilePos {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
#[cfg(all(unix, feature = "std-io"))]
impl From<u64> for FilePos {
#[inline(always)]
fn from(x: u64) -> Self {
if x > libc::off64_t::MAX as u64 {
unreachable!("could not convert {x} to file offset! (too large)");
}
Self(x as libc::off64_t)
}
}
#[cfg(all(unix, feature = "std-io"))]
impl ops::AddAssign<usize> for FilePos {
#[inline]
fn add_assign(&mut self, rhs: usize) {
if rhs > libc::off64_t::MAX as usize {
unreachable!("could not convert {rhs} to file offset in +=! (too large)");
}
self.0 = match self.0.checked_add(rhs as libc::off64_t) {
None => unreachable!("could not add {rhs} to file offset in +=! (would become too large)"),
Some(n) => n,
};
}
}
pub trait PReadBuf<T> {
type Pos: ops::AddAssign<usize>+Copy;
type E: BufError;
fn pread_buf<'buf>(
&self,
pos: Self::Pos,
buf: &'buf mut [mem::MaybeUninit<T>],
) -> (usize, Option<Self::E>);
#[inline]
fn pread_buf_exact<'buf>(
&self,
mut pos: Self::Pos,
mut buf: &'buf mut [mem::MaybeUninit<T>],
) -> Result<(), (usize, Self::E)> {
let mut all_pread: usize = 0;
while !buf.is_empty() {
let (cur_pread, err) = self.pread_buf(pos, buf);
all_pread += cur_pread;
if let Some(err) = err {
if err.would_block() {
unreachable!("pread_buf() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_pread, err));
}
}
buf = &mut buf[cur_pread..];
pos += cur_pread;
}
Ok(())
}
}
#[cfg(all(unix, feature = "std-io"))]
impl PReadBuf<u8> for File {
type Pos = FilePos;
type E = io::Error;
#[inline]
fn pread_buf<'buf>(
&self,
pos: Self::Pos,
buf: &'buf mut [mem::MaybeUninit<u8>],
) -> (usize, Option<Self::E>) {
let fd = self.as_raw_fd();
let FilePos(offset) = pos;
let count = buf.len();
let buf: *mut libc::c_void = buf.as_mut_ptr().cast();
match unsafe { libc::pread64(fd, buf, count, offset) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait PWriteData<T> {
type Pos: ops::AddAssign<usize>+Copy;
type E: BufError;
fn pwrite_data<'data>(&mut self, pos: Self::Pos, data: &'data [T]) -> (usize, Option<Self::E>);
#[inline]
fn pwrite_data_exact<'data>(
&mut self,
mut pos: Self::Pos,
mut data: &'data [T],
) -> Result<(), (usize, Self::E)> {
let mut all_pwritten: usize = 0;
while !data.is_empty() {
let (cur_pwritten, err) = self.pwrite_data(pos, data);
all_pwritten += cur_pwritten;
if let Some(err) = err {
if err.would_block() {
unreachable!("pwrite_data() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_pwritten, err));
}
}
data = &data[cur_pwritten..];
pos += cur_pwritten;
}
Ok(())
}
}
#[cfg(all(unix, feature = "std-io"))]
impl PWriteData<u8> for File {
type Pos = FilePos;
type E = io::Error;
#[inline]
fn pwrite_data<'data>(&mut self, pos: Self::Pos, data: &'data [u8]) -> (usize, Option<Self::E>) {
let fd = self.as_raw_fd();
let FilePos(offset) = pos;
let count = data.len();
let buf: *const libc::c_void = data.as_ptr().cast();
match unsafe { libc::pwrite64(fd, buf, count, offset) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait Splice<P> {
type E: BufError;
fn splice(&mut self, to: &mut P, n: usize) -> (usize, Option<Self::E>);
#[inline]
fn splice_exact(&mut self, to: &mut P, mut n: usize) -> Result<(), (usize, Self::E)> {
let mut all_spliced: usize = 0;
while n > 0 {
let (cur_spliced, err) = self.splice(to, n);
all_spliced += cur_spliced;
if let Some(err) = err {
if err.would_block() {
unreachable!("splice() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_spliced, err));
}
}
n -= cur_spliced;
}
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl Splice<BlockingPipe<PipeWriter>> for BlockingPipe<PipeReader> {
type E = io::Error;
#[inline]
fn splice(&mut self, to: &mut BlockingPipe<PipeWriter>, n: usize) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe { libc::splice(in_fd, ptr::null_mut(), out_fd, ptr::null_mut(), n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl Splice<BlockingPipe<PipeWriter>> for File {
type E = io::Error;
#[inline]
fn splice(&mut self, to: &mut BlockingPipe<PipeWriter>, n: usize) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe { libc::splice(in_fd, ptr::null_mut(), out_fd, ptr::null_mut(), n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl Splice<File> for BlockingPipe<PipeReader> {
type E = io::Error;
#[inline]
fn splice(&mut self, to: &mut File, n: usize) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe { libc::splice(in_fd, ptr::null_mut(), out_fd, ptr::null_mut(), n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait SpliceNonBlocking<P> {
type E: BufError;
fn splice_nonblocking(&mut self, to: &mut P, n: usize) -> (usize, Option<Self::E>);
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl SpliceNonBlocking<NonBlockingPipe<PipeWriter>> for NonBlockingPipe<PipeReader> {
type E = io::Error;
#[inline]
fn splice_nonblocking(
&mut self,
to: &mut NonBlockingPipe<PipeWriter>,
n: usize,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe {
libc::splice(
in_fd,
ptr::null_mut(),
out_fd,
ptr::null_mut(),
n,
libc::SPLICE_F_NONBLOCK,
)
} {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait SpliceAt<P> {
type E: BufError;
type Pos: ops::AddAssign<usize>+Copy;
fn splice_at(&self, start: &mut Self::Pos, n: usize, to: &mut P) -> (usize, Option<Self::E>);
#[inline]
fn splice_at_exact(
&self,
start: &mut Self::Pos,
mut n: usize,
to: &mut P,
) -> Result<(), (usize, Self::E)> {
let mut all_spliced: usize = 0;
while n > 0 {
let (cur_spliced, err) = self.splice_at(start, n, to);
all_spliced += cur_spliced;
if let Some(err) = err {
if err.would_block() {
unreachable!("splice() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_spliced, err));
}
}
n -= cur_spliced;
}
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl SpliceAt<BlockingPipe<PipeWriter>> for File {
type E = io::Error;
type Pos = FilePos;
#[inline]
fn splice_at(
&self,
start: &mut Self::Pos,
n: usize,
to: &mut BlockingPipe<PipeWriter>,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
let &mut FilePos(mut in_off) = start;
let in_off: *mut libc::off64_t = ptr::from_mut(&mut in_off);
match unsafe { libc::splice(in_fd, in_off, out_fd, ptr::null_mut(), n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait SpliceToAt<P> {
type E: BufError;
type Pos: ops::AddAssign<usize>+Copy;
fn splice_to_at(&mut self, n: usize, to: &mut P, at: &mut Self::Pos) -> (usize, Option<Self::E>);
#[inline]
fn splice_to_at_exact(
&mut self,
mut n: usize,
to: &mut P,
at: &mut Self::Pos,
) -> Result<(), (usize, Self::E)> {
let mut all_spliced: usize = 0;
while n > 0 {
let (cur_spliced, err) = self.splice_to_at(n, to, at);
all_spliced += cur_spliced;
if let Some(err) = err {
if err.would_block() {
unreachable!("splice() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_spliced, err));
}
}
n -= cur_spliced;
}
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl SpliceToAt<File> for BlockingPipe<PipeReader> {
type E = io::Error;
type Pos = FilePos;
#[inline]
fn splice_to_at(
&mut self,
n: usize,
to: &mut File,
at: &mut Self::Pos,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
let &mut FilePos(mut out_off) = at;
let out_off: *mut libc::off64_t = ptr::from_mut(&mut out_off);
match unsafe { libc::splice(in_fd, ptr::null_mut(), out_fd, out_off, n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait Tee<P> {
type E: BufError;
fn tee(&self, to: &mut P, n: usize) -> (usize, Option<Self::E>);
#[inline]
fn tee_exact(&self, to: &mut P, mut n: usize) -> Result<(), (usize, Self::E)> {
let mut all_teed: usize = 0;
while n > 0 {
let (cur_teed, err) = self.tee(to, n);
all_teed += cur_teed;
if let Some(err) = err {
if err.would_block() {
unreachable!("tee() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_teed, err));
}
}
n -= cur_teed;
}
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl Tee<BlockingPipe<PipeWriter>> for BlockingPipe<PipeReader> {
type E = io::Error;
#[inline]
fn tee(&self, to: &mut BlockingPipe<PipeWriter>, n: usize) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe { libc::tee(in_fd, out_fd, n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait TeeNonBlocking<P> {
type E: BufError;
fn tee_nonblocking(&self, to: &mut P, n: usize) -> (usize, Option<Self::E>);
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl TeeNonBlocking<NonBlockingPipe<PipeWriter>> for NonBlockingPipe<PipeReader> {
type E = io::Error;
#[inline]
fn tee_nonblocking(
&self,
to: &mut NonBlockingPipe<PipeWriter>,
n: usize,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
match unsafe { libc::tee(in_fd, out_fd, n, libc::SPLICE_F_NONBLOCK) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}
pub trait CopyRange<P> {
type Pos: ops::AddAssign<usize>+Copy;
type E: BufError;
fn copy_range(
&self,
start: &mut Self::Pos,
n: usize,
to: &mut P,
pos: Option<&mut Self::Pos>,
) -> (usize, Option<Self::E>);
#[inline]
fn copy_range_exact(
&self,
start: &mut Self::Pos,
mut n: usize,
to: &mut P,
pos: Option<&mut Self::Pos>,
) -> Result<(), (usize, Self::E)> {
let mut all_copied: usize = 0;
let pos_ptr: Option<ptr::NonNull<Self::Pos>> = pos.map(ptr::NonNull::from_mut);
while n > 0 {
let (cur_copied, err) =
self.copy_range(start, n, to, pos_ptr.map(|mut pos| unsafe { pos.as_mut() }));
all_copied += cur_copied;
if let Some(err) = err {
if err.would_block() {
unreachable!("copy_range() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_copied, err));
}
}
n -= cur_copied;
}
Ok(())
}
fn copy_range_mut(
&mut self,
n: usize,
to: &mut P,
pos: Option<&mut Self::Pos>,
) -> (usize, Option<Self::E>);
#[inline]
fn copy_range_exact_mut(
&mut self,
mut n: usize,
to: &mut P,
pos: Option<&mut Self::Pos>,
) -> Result<(), (usize, Self::E)> {
let mut all_copied: usize = 0;
let pos_ptr: Option<ptr::NonNull<Self::Pos>> = pos.map(ptr::NonNull::from_mut);
while n > 0 {
let (cur_copied, err) =
self.copy_range_mut(n, to, pos_ptr.map(|mut pos| unsafe { pos.as_mut() }));
all_copied += cur_copied;
if let Some(err) = err {
if err.would_block() {
unreachable!("copy_range() is allowed to block!");
}
if !err.was_interrupted() {
return Err((all_copied, err));
}
}
n -= cur_copied;
}
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "std-io"))]
impl CopyRange<File> for File {
type Pos = FilePos;
type E = io::Error;
#[inline]
fn copy_range(
&self,
start: &mut Self::Pos,
n: usize,
to: &mut File,
mut pos: Option<&mut Self::Pos>,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
let &mut FilePos(mut in_off) = start;
let in_off: *mut libc::off64_t = ptr::from_mut(&mut in_off);
let out_off: *mut libc::off64_t = match pos.as_mut() {
None => ptr::null_mut(),
Some(off) => ptr::from_mut(off),
};
match unsafe { libc::copy_file_range(in_fd, in_off, out_fd, out_off, n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
#[inline]
fn copy_range_mut(
&mut self,
n: usize,
to: &mut File,
mut pos: Option<&mut Self::Pos>,
) -> (usize, Option<Self::E>) {
let in_fd = self.as_raw_fd();
let out_fd = to.as_raw_fd();
let out_off: *mut libc::off64_t = match pos.as_mut() {
None => ptr::null_mut(),
Some(off) => ptr::from_mut(off),
};
match unsafe { libc::copy_file_range(in_fd, ptr::null_mut(), out_fd, out_off, n, 0) } {
-1 => (0, Some(io::Error::last_os_error())),
n => (n as usize, None),
}
}
}