1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-05 07:27:46 +00:00

uucore, yes: add zero-copy on supported platforms (only Linux currently)

This commit is contained in:
Alex Lyon 2019-05-01 04:43:14 -07:00 committed by Roy Ivy III
parent d81d3e3c71
commit 72c322a882
8 changed files with 341 additions and 0 deletions

View file

@ -11,6 +11,9 @@ time = { version = "0.1.40", optional = true }
data-encoding = { version = "^2.1", optional = true }
libc = { version = "0.2.42", optional = true }
wild = "2.0.1"
nix = { version = "0.13", optional = true }
lazy_static = { version = "1.3", optional = true }
platform-info = { version = "0.0.1", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
termion = "1.5"
@ -25,6 +28,7 @@ utmpx = ["time", "libc"]
process = ["libc"]
signals = []
entries = ["libc"]
zero-copy = ["nix", "libc", "lazy_static", "platform-info"]
wide = []
default = []

View file

@ -13,6 +13,13 @@ extern crate failure;
#[cfg(feature = "failure_derive")]
#[macro_use]
extern crate failure_derive;
#[cfg(feature = "nix")]
extern crate nix;
#[cfg(all(feature = "lazy_static", target_os = "linux"))]
#[macro_use]
extern crate lazy_static;
#[cfg(feature = "platform-info")]
extern crate platform_info;
#[macro_use]
mod macros;
@ -40,5 +47,8 @@ pub mod process;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub mod signals;
#[cfg(feature = "zero-copy")]
pub mod zero_copy;
#[cfg(all(windows, feature = "wide"))]
pub mod wide;

139
src/uucore/zero_copy/mod.rs Normal file
View file

@ -0,0 +1,139 @@
use self::platform::*;
use std::io::{self, Write};
mod platform;
pub trait AsRawObject {
fn as_raw_object(&self) -> RawObject;
}
pub trait FromRawObject : Sized {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self>;
}
// TODO: also make a SpliceWriter that takes an input fd and and output fd and uses splice() to
// transfer data
// TODO: make a TeeWriter or something that takes an input fd and two output fds and uses tee() to
// transfer to both output fds
enum InnerZeroCopyWriter<T: Write + Sized> {
Platform(PlatformZeroCopyWriter),
Standard(T),
}
impl<T: Write + Sized> Write for InnerZeroCopyWriter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
InnerZeroCopyWriter::Platform(ref mut writer) => writer.write(buf),
InnerZeroCopyWriter::Standard(ref mut writer) => writer.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
InnerZeroCopyWriter::Platform(ref mut writer) => writer.flush(),
InnerZeroCopyWriter::Standard(ref mut writer) => writer.flush(),
}
}
}
pub struct ZeroCopyWriter<T: Write + AsRawObject + Sized> {
/// This field is never used, but we need it to drop file descriptors
#[allow(dead_code)]
raw_obj_owner: Option<T>,
inner: InnerZeroCopyWriter<T>,
}
struct TransformContainer<'a, A: Write + AsRawObject + Sized, B: Write + Sized> {
/// This field is never used and probably could be converted into PhantomData, but might be
/// useful for restructuring later (at the moment it's basically left over from an earlier
/// design)
#[allow(dead_code)]
original: Option<&'a mut A>,
transformed: Option<B>,
}
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> Write for TransformContainer<'a, A, B> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.transformed.as_mut().unwrap().write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.transformed.as_mut().unwrap().flush()
}
}
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> AsRawObject for TransformContainer<'a, A, B> {
fn as_raw_object(&self) -> RawObject {
panic!("Test should never be used")
}
}
impl<T: Write + AsRawObject + Sized> ZeroCopyWriter<T> {
pub fn new(writer: T) -> Self {
let raw_obj = writer.as_raw_object();
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
Ok(inner) => {
ZeroCopyWriter {
raw_obj_owner: Some(writer),
inner: InnerZeroCopyWriter::Platform(inner),
}
}
_ => {
// creating the splice writer failed for whatever reason, so just make a default
// writer
ZeroCopyWriter {
raw_obj_owner: None,
inner: InnerZeroCopyWriter::Standard(writer),
}
}
}
}
pub fn with_default<'a: 'b, 'b, F, W>(writer: &'a mut T, func: F) -> ZeroCopyWriter<impl Write + AsRawObject + Sized + 'b>
where
F: Fn(&'a mut T) -> W,
W: Write + Sized + 'b,
{
let raw_obj = writer.as_raw_object();
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
Ok(inner) => {
ZeroCopyWriter {
raw_obj_owner: Some(TransformContainer { original: Some(writer), transformed: None, }),
inner: InnerZeroCopyWriter::Platform(inner),
}
}
_ => {
// XXX: should func actually consume writer and leave it up to the user to save the value?
// maybe provide a default stdin method then? in some cases it would make more sense for the
// value to be consumed
let real_writer = func(writer);
ZeroCopyWriter {
raw_obj_owner: None,
inner: InnerZeroCopyWriter::Standard(TransformContainer { original: None, transformed: Some(real_writer) }),
}
}
}
}
// XXX: unsure how to get something like this working without allocating, so not providing it
/*pub fn stdout() -> ZeroCopyWriter<impl Write + AsRawObject + Sized> {
let mut stdout = io::stdout();
ZeroCopyWriter::with_default(&mut stdout, |stdout| {
stdout.lock()
})
}*/
}
impl<T: Write + AsRawObject + Sized> Write for ZeroCopyWriter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}

View file

@ -0,0 +1,25 @@
use crate::zero_copy::RawObject;
use std::io::{self, Write};
/// A "zero-copy" writer used on platforms for which we have no actual zero-copy implementation (or
/// which use standard read/write operations for zero-copy I/O). This writer just delegates to the
/// inner writer used to create it. Using this struct avoids going through the machinery used to
/// handle the case where a given writer does not support zero-copy on a platform.
pub struct PlatformZeroCopyWriter;
impl PlatformZeroCopyWriter {
pub unsafe fn new(_obj: RawObject) -> Result<Self, ()> {
Err(())
}
}
impl Write for PlatformZeroCopyWriter {
fn write(&mut self, _bytes: &[u8]) -> io::Result<usize> {
panic!("should never occur")
}
fn flush(&mut self) -> io::Result<()> {
panic!("should never occur")
}
}

View file

@ -0,0 +1,105 @@
use std::io::{self, Write};
use std::os::unix::io::RawFd;
use libc::{O_APPEND, S_IFIFO, S_IFREG};
use nix::errno::Errno;
use nix::fcntl::{fcntl, splice, vmsplice, FcntlArg, SpliceFFlags};
use nix::sys::uio::IoVec;
use nix::sys::stat::{fstat, FileStat};
use nix::unistd::pipe;
use platform_info::{Uname, PlatformInfo};
use crate::zero_copy::{FromRawObject, RawObject};
lazy_static! {
static ref IN_WSL: bool = {
let info = PlatformInfo::new().unwrap();
info.release().contains("Microsoft")
};
}
pub struct PlatformZeroCopyWriter {
raw_obj: RawObject,
read_pipe: RawFd,
write_pipe: RawFd,
write_fn: fn(&mut PlatformZeroCopyWriter, &[IoVec<&[u8]>], usize) -> io::Result<usize>,
}
impl PlatformZeroCopyWriter {
pub unsafe fn new(raw_obj: RawObject) -> nix::Result<Self> {
if *IN_WSL {
// apparently WSL hasn't implemented vmsplice(), causing writes to fail
// thus, we will just say zero-copy doesn't work there rather than working
// around it
return Err(nix::Error::from(Errno::EOPNOTSUPP))
}
let stat_info: FileStat = fstat(raw_obj)?;
let access_mode: libc::c_int = fcntl(raw_obj, FcntlArg::F_GETFL)?;
let is_regular = (stat_info.st_mode & S_IFREG) != 0;
let is_append = (access_mode & O_APPEND) != 0;
let is_fifo = (stat_info.st_mode & S_IFIFO) != 0;
if is_regular && !is_append {
let (read_pipe, write_pipe) = pipe()?;
Ok(PlatformZeroCopyWriter {
raw_obj,
read_pipe,
write_pipe,
write_fn: write_regular,
})
} else if is_fifo {
Ok(PlatformZeroCopyWriter {
raw_obj,
read_pipe: Default::default(),
write_pipe: Default::default(),
write_fn: write_fifo,
})
} else {
// FIXME: how to error?
Err(nix::Error::from(Errno::UnknownErrno))
}
}
}
impl FromRawObject for PlatformZeroCopyWriter {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
PlatformZeroCopyWriter::new(obj).ok()
}
}
impl Write for PlatformZeroCopyWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let iovec = &[IoVec::from_slice(buf)];
let func = self.write_fn;
func(self, iovec, buf.len())
}
fn flush(&mut self) -> io::Result<()> {
// XXX: not sure if we need anything else
Ok(())
}
}
fn write_regular(writer: &mut PlatformZeroCopyWriter, iovec: &[IoVec<&[u8]>], len: usize) -> io::Result<usize> {
vmsplice(writer.write_pipe, iovec, SpliceFFlags::empty())
.and_then(|_|
splice(
writer.read_pipe,
None,
writer.raw_obj,
None,
len,
SpliceFFlags::empty()
)
)
.map_err(|_| io::Error::last_os_error())
}
fn write_fifo(writer: &mut PlatformZeroCopyWriter, iovec: &[IoVec<&[u8]>], _len: usize) -> io::Result<usize> {
vmsplice(writer.raw_obj, iovec, SpliceFFlags::empty())
.map_err(|_| io::Error::last_os_error())
}

View file

@ -0,0 +1,21 @@
#[cfg(unix)]
pub use self::unix::*;
#[cfg(target_os = "linux")]
pub use self::linux::*;
#[cfg(windows)]
pub use self::windows::*;
// Add any operating systems we support here
#[cfg(not(any(target_os = "linux")))]
pub use self::default::*;
#[cfg(unix)]
mod unix;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(windows)]
mod windows;
// Add any operating systems we support here
#[cfg(not(any(target_os = "linux")))]
mod default;

View file

@ -0,0 +1,18 @@
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use crate::zero_copy::{AsRawObject, FromRawObject};
pub type RawObject = RawFd;
impl<T: AsRawFd> AsRawObject for T {
fn as_raw_object(&self) -> RawObject {
self.as_raw_fd()
}
}
// FIXME: check if this works right
impl<T: FromRawFd> FromRawObject for T {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
Some(T::from_raw_fd(obj))
}
}

View file

@ -0,0 +1,19 @@
use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
use crate::zero_copy::{AsRawObject, FromRawObject};
pub type RawObject = RawHandle;
impl<T: AsRawHandle> AsRawObject for T {
fn as_raw_object(&self) -> RawObject {
self.as_raw_handle()
}
}
impl<T: FromRawHandle> FromRawObject for T {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
Some(T::from_raw_handle(obj))
}
}
// TODO: see if there's some zero-copy stuff in Windows